From 351f18f255a85607a6acf5312ab5e2a49950cea5 Mon Sep 17 00:00:00 2001 From: luch <32321690+luch1994@users.noreply.github.com> Date: Fri, 27 Sep 2024 19:52:01 +0800 Subject: [PATCH] =?UTF-8?q?[Feature]=20=E6=96=B0=E5=A2=9E=E6=9A=82?= =?UTF-8?q?=E5=81=9C=E5=8A=9F=E8=83=BD=E4=BB=A5=E5=8F=8A=E5=A4=9A=E8=A1=A8?= =?UTF-8?q?=E5=AD=97=E6=AE=B5=E5=8D=87=E7=BA=A7=20(#434)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat:新增暂停功能 (#416) * feat:新增暂停功能 * feat: 修改状态相关功能 (#433) Co-authored-by: luchunhui * fix: 修改刷数据逻辑错误和环境变量问题 * fix: 解决lint问题 --------- Co-authored-by: chaorenluo <1243357953@qq.com> Co-authored-by: luchunhui --- DATA_COLLECTION.md | 22 ++ README.md | 2 +- docker-compose.yaml | 2 +- server/.env | 2 +- server/.env.development | 20 ++ server/package.json | 9 +- server/scripts/run-report.ts | 62 ++++++ server/src/app.module.ts | 11 +- server/src/enums/downloadTaskStatus.ts | 6 + server/src/enums/exceptionCode.ts | 1 + server/src/enums/index.ts | 15 +- server/src/enums/surveySessionStatus.ts | 4 + server/src/guards/session.guard.ts | 1 + server/src/interfaces/survey.ts | 2 +- server/src/logger/index.ts | 15 +- server/src/logger/util.ts | 4 +- server/src/main.ts | 1 + .../src/middlewares/logRequest.middleware.ts | 2 +- server/src/models/base.entity.ts | 40 +--- server/src/models/collaborator.entity.ts | 12 + server/src/models/downloadTask.entity.ts | 10 + .../src/models/messagePushingTask.entity.ts | 12 + server/src/models/responseSchema.entity.ts | 16 ++ server/src/models/session.entity.ts | 4 + server/src/models/surveyHistory.entity.ts | 3 + server/src/models/surveyMeta.entity.ts | 50 ++++- server/src/models/workspace.entity.ts | 21 ++ server/src/models/workspaceMember.entity.ts | 12 + .../modules/auth/__test/user.service.spec.ts | 7 - .../src/modules/auth/services/user.service.ts | 17 +- .../__test/messagePushingTask.service.spec.ts | 27 +-- .../messagePushingTask.controller.ts | 3 +- .../message/dto/messagePushingTask.dto.ts | 7 - .../dto/updateMessagePushingTask.dto.ts | 7 - .../services/messagePushingTask.service.ts | 38 ++-- .../__test/dataStatistic.controller.spec.ts | 2 +- .../__test/downloadTask.service.spec.ts | 1 - .../survey/__test/mockResponseSchema.ts | 8 +- .../survey/__test/survey.controller.spec.ts | 2 +- .../__test/surveyHistory.service.spec.ts | 2 +- .../__test/surveyMeta.controller.spec.ts | 11 +- .../survey/__test/surveyMeta.service.spec.ts | 21 +- .../controllers/collaborator.controller.ts | 6 + .../controllers/downloadTask.controller.ts | 6 +- .../survey/controllers/session.controller.ts | 4 +- .../survey/controllers/survey.controller.ts | 42 +++- .../controllers/surveyHistory.controller.ts | 2 +- .../controllers/surveyMeta.controller.ts | 11 +- .../survey/services/collaborator.service.ts | 23 +- .../survey/services/dataStatistic.service.ts | 18 +- .../survey/services/downloadTask.service.ts | 80 +++---- .../survey/services/session.service.ts | 28 +-- .../survey/services/surveyHistory.service.ts | 4 +- .../survey/services/surveyMeta.service.ts | 92 +++++--- .../template/surveyTemplate/templateBase.json | 7 +- server/src/modules/survey/utils/index.ts | 4 +- .../__test/clientEncrypt.service.spec.ts | 4 - .../__test/mockResponseSchema.ts | 10 +- .../__test/responseSchema.controller.spec.ts | 20 +- .../__test/surveyResponse.controller.spec.ts | 5 +- .../__test/surveyResponse.service.spec.ts | 2 +- .../controllers/responseSchema.controller.ts | 18 +- .../controllers/surveyResponse.controller.ts | 17 +- .../services/clientEncrypt.service.ts | 20 +- .../services/counter.service.ts | 1 + .../services/responseScheme.service.ts | 40 +++- .../services/surveyResponse.service.ts | 5 +- .../upgrade/controllers/upgrade.controller.ts | 21 ++ .../upgrade/services/upgrade.service.ts | 210 ++++++++++++++++++ server/src/modules/upgrade/upgrade.module.ts | 51 +++++ .../_test/workspace.controller.spec.ts | 12 +- .../controllers/workspace.controller.ts | 36 ++- .../controllers/workspaceMember.controller.ts | 9 +- .../workspace/services/workspace.service.ts | 61 ++--- .../services/workspaceMember.service.ts | 38 +++- server/src/utils/surveyUtil.ts | 3 +- web/package.json | 8 +- web/report.ts | 47 ++++ web/src/management/api/analysis.js | 1 + web/src/management/api/survey.js | 12 +- web/src/management/config/listConfig.js | 67 +++--- .../pages/analysis/components/DataTable.vue | 2 +- .../pages/analysis/pages/DataTablePage.vue | 2 +- .../pages/download/components/TaskList.vue | 24 +- web/src/management/pages/edit/index.vue | 1 + .../modules/contentModule/HistoryPanel.vue | 2 +- .../modules/contentModule/PublishPanel.vue | 1 - .../edit/modules/contentModule/SavePanel.vue | 1 - .../pages/edit/setterConfig/baseFormConfig.js | 4 +- .../pages/list/components/BaseList.vue | 89 ++++---- .../pages/list/components/SpaceList.vue | 2 +- .../pages/list/components/StateModule.vue | 32 +-- web/src/management/pages/list/index.vue | 1 + web/src/management/stores/edit.ts | 1 - web/src/management/stores/surveyList.ts | 29 ++- web/src/management/utils/workSpace.ts | 2 +- .../questions/widgets/BaseChoice/index.jsx | 2 +- .../widgets/CheckboxModule/index.jsx | 3 +- .../questions/widgets/CheckboxModule/meta.js | 4 +- .../materials/setters/widgets/InputNumber.vue | 1 + .../setters/widgets/QuestionTime.vue | 10 +- web/src/render/api/survey.js | 1 + .../VerifyDialog/WhiteListDialog.vue | 10 +- web/src/render/hooks/useProgress.js | 2 +- web/src/render/pages/IndexPage.vue | 9 +- web/src/render/pages/RenderPage.vue | 1 - web/src/render/stores/question.js | 3 + web/src/render/stores/survey.js | 9 +- web/tsconfig.node.json | 3 +- web/vite.config.ts | 2 + 110 files changed, 1287 insertions(+), 525 deletions(-) create mode 100644 DATA_COLLECTION.md create mode 100644 server/scripts/run-report.ts create mode 100644 server/src/enums/downloadTaskStatus.ts create mode 100644 server/src/enums/surveySessionStatus.ts create mode 100644 server/src/modules/upgrade/controllers/upgrade.controller.ts create mode 100644 server/src/modules/upgrade/services/upgrade.service.ts create mode 100644 server/src/modules/upgrade/upgrade.module.ts create mode 100644 web/report.ts diff --git a/DATA_COLLECTION.md b/DATA_COLLECTION.md new file mode 100644 index 00000000..5f13bd5e --- /dev/null +++ b/DATA_COLLECTION.md @@ -0,0 +1,22 @@ +# Important Disclosure re:XIAOJUSURVEY Data Collection + +XIAOJUSURVEY is open-source software developed and maintained by XIAOJUSURVEY Team and available at https://github.com/didi/xiaoju-survey. +We hereby state the purpose and reason for collecting data. + +## Purpose of data collection + +Data collected is used to help improve XIAOJUSURVEY for all users. It is important that our team understands the usage patterns as soon as possible, so we can best decide how to design future features and prioritize current work. + +## Types of data collected + +XIAOJUSURVEY just collects data about version's information. The data collected is subsequently reported to the XIAOJUSURVEY's backend services. + +All data collected will be used exclusively by the XIAOJUSURVEY team for analytical purposes only. The data will be neither accessible nor sold to any third party. + +## Sensitive data + +XIAOJUSURVEY will never collect and/or report sensitive information, such as private keys, API keys, or passwords. + +## How do I opt-in to or opt-out of data sharing? + +See [docs](https://xiaojusurvey.didi.cn/docs/next/community/%E6%95%B0%E6%8D%AE%E4%B8%8A%E6%8A%A5%E5%A3%B0%E6%98%8E) for information on configuring this functionality. diff --git a/README.md b/README.md index c146e264..e3e1c89d 100644 --- a/README.md +++ b/README.md @@ -169,4 +169,4 @@ _任何问题和合作可以联系小助手。_ ## CHANGELOG -关注重大项目变更:[MAJOR CHANGELOG](https://github.com/didi/xiaoju-survey/issues/48)。 +关注重大项目变更:[MAJOR CHANGELOG](https://github.com/didi/xiaoju-survey/issues/48)。 \ No newline at end of file diff --git a/docker-compose.yaml b/docker-compose.yaml index 33e37dbf..6f5666b7 100644 --- a/docker-compose.yaml +++ b/docker-compose.yaml @@ -15,7 +15,7 @@ services: - xiaoju-survey xiaoju-survey: - image: "xiaojusurvey/xiaoju-survey:1.1.6-slim" # 最新版本:https://hub.docker.com/r/xiaojusurvey/xiaoju-survey/tags + image: "xiaojusurvey/xiaoju-survey:1.2.0-slim" # 最新版本:https://hub.docker.com/r/xiaojusurvey/xiaoju-survey/tags container_name: xiaoju-survey restart: always ports: diff --git a/server/.env b/server/.env index f534d4ca..ae1549d8 100644 --- a/server/.env +++ b/server/.env @@ -1,5 +1,5 @@ XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey -XIAOJU_SURVEY_MONGO_URL=mongodb://127.0.0.1:27017 +XIAOJU_SURVEY_MONGO_URL= XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin XIAOJU_SURVEY_REDIS_HOST= diff --git a/server/.env.development b/server/.env.development index e69de29b..d5d1d565 100644 --- a/server/.env.development +++ b/server/.env.development @@ -0,0 +1,20 @@ +XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey +XIAOJU_SURVEY_MONGO_URL=mongodb://127.0.0.1:27017 +XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin + +XIAOJU_SURVEY_REDIS_HOST= +XIAOJU_SURVEY_REDIS_PORT= +XIAOJU_SURVEY_REDIS_USERNAME= +XIAOJU_SURVEY_REDIS_PASSWORD= +XIAOJU_SURVEY_REDIS_DB= + + +XIAOJU_SURVEY_RESPONSE_AES_ENCRYPT_SECRET_KEY=dataAesEncryptSecretKey +XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa + +XIAOJU_SURVEY_JWT_SECRET=xiaojuSurveyJwtSecret +XIAOJU_SURVEY_JWT_EXPIRES_IN=8h + +XIAOJU_SURVEY_LOGGER_FILENAME=./logs/app.log + +XIAOJU_SURVEY_REPORT=true diff --git a/server/package.json b/server/package.json index 5222380a..27a929ca 100644 --- a/server/package.json +++ b/server/package.json @@ -1,7 +1,7 @@ { - "name": "server", - "version": "0.0.1", - "description": "", + "name": "xiaoju-survey-server", + "version": "1.3.0", + "description": "XIAOJUSURVEY的server端", "author": "", "scripts": { "build": "nest build", @@ -22,6 +22,7 @@ "@nestjs/common": "^10.0.0", "@nestjs/config": "^3.1.1", "@nestjs/core": "^10.0.0", + "@nestjs/microservices": "^10.4.4", "@nestjs/platform-express": "^10.0.0", "@nestjs/serve-static": "^4.0.0", "@nestjs/swagger": "^7.3.0", @@ -75,7 +76,7 @@ "prettier": "^3.0.0", "redis-memory-server": "^0.11.0", "source-map-support": "^0.5.21", - "supertest": "^6.3.3", + "supertest": "^7.0.0", "ts-jest": "^29.1.0", "ts-loader": "^9.4.3", "ts-node": "^10.9.1", diff --git a/server/scripts/run-report.ts b/server/scripts/run-report.ts new file mode 100644 index 00000000..f26306e5 --- /dev/null +++ b/server/scripts/run-report.ts @@ -0,0 +1,62 @@ +import fs, { promises as fsa } from 'fs-extra'; +import path from 'path'; +import fetch from 'node-fetch'; + +interface PackageJson { + type?: string; + name?: string; + version?: string; + description?: string; + id?: string; + msg?: string; +} + +const getId = () => { + const id = new Date().getTime().toString(); + process.env.XIAOJU_SURVEY_REPORT_ID = id; + + return id; +}; + +const readData = async (directory: string): Promise => { + const packageJsonPath = path.join(directory, 'package.json'); + const id = process.env.XIAOJU_SURVEY_REPORT_ID || getId(); + try { + if (!fs.existsSync(directory)) { + return { + type: 'server', + name: '', + version: '', + description: '', + id, + msg: '文件不存在', + }; + } + const data = await fsa.readFile(packageJsonPath, 'utf8').catch((e) => e); + const { name, version, description } = JSON.parse(data) as PackageJson; + return { type: 'server', name, version, description, id }; + } catch (error) { + return error; + } +}; + +(async (): Promise => { + if ( + process.env.NODE_ENV === 'development' && + !process.env.XIAOJU_SURVEY_REPORT + ) { + return; + } + + const res = await readData(path.join(process.cwd())); + + // 上报 + fetch('https://xiaojusurveysrc.didi.cn/reportSourceData', { + method: 'POST', + headers: { + Accept: 'application/json, */*', + 'Content-Type': 'application/json', + }, + body: JSON.stringify(res), + }).catch(() => {}); +})(); diff --git a/server/src/app.module.ts b/server/src/app.module.ts index d9ea22e3..100eabed 100644 --- a/server/src/app.module.ts +++ b/server/src/app.module.ts @@ -14,6 +14,7 @@ import { AuthModule } from './modules/auth/auth.module'; import { MessageModule } from './modules/message/message.module'; import { FileModule } from './modules/file/file.module'; import { WorkspaceModule } from './modules/workspace/workspace.module'; +import { UpgradeModule } from './modules/upgrade/upgrade.module'; import { join } from 'path'; @@ -35,18 +36,21 @@ import { MessagePushingLog } from './models/messagePushingLog.entity'; import { WorkspaceMember } from './models/workspaceMember.entity'; import { Workspace } from './models/workspace.entity'; import { Collaborator } from './models/collaborator.entity'; +import { DownloadTask } from './models/downloadTask.entity'; +import { Session } from './models/session.entity'; import { LoggerProvider } from './logger/logger.provider'; import { PluginManagerProvider } from './securityPlugin/pluginManager.provider'; import { LogRequestMiddleware } from './middlewares/logRequest.middleware'; import { PluginManager } from './securityPlugin/pluginManager'; import { Logger } from './logger'; -import { DownloadTask } from './models/downloadTask.entity'; -import { Session } from './models/session.entity'; @Module({ imports: [ - ConfigModule.forRoot({}), + ConfigModule.forRoot({ + envFilePath: `.env.${process.env.NODE_ENV}`, // 根据 NODE_ENV 动态加载对应的 .env 文件 + isGlobal: true, // 使配置模块在应用的任何地方可用 + }), TypeOrmModule.forRootAsync({ imports: [ConfigModule], inject: [ConfigService], @@ -104,6 +108,7 @@ import { Session } from './models/session.entity'; MessageModule, FileModule, WorkspaceModule, + UpgradeModule, ], controllers: [AppController], providers: [ diff --git a/server/src/enums/downloadTaskStatus.ts b/server/src/enums/downloadTaskStatus.ts new file mode 100644 index 00000000..1ea35832 --- /dev/null +++ b/server/src/enums/downloadTaskStatus.ts @@ -0,0 +1,6 @@ +export enum DOWNLOAD_TASK_STATUS { + WAITING = 'waiting', // 排队中 + COMPUTING = 'computing', // 计算中 + SUCCEED = 'succeed', // 导出成功 + FAILED = 'failed', // 导出失败 +} diff --git a/server/src/enums/exceptionCode.ts b/server/src/enums/exceptionCode.ts index 8ead165e..e2285e93 100644 --- a/server/src/enums/exceptionCode.ts +++ b/server/src/enums/exceptionCode.ts @@ -21,6 +21,7 @@ export enum EXCEPTION_CODE { RESPONSE_OVER_LIMIT = 9003, // 超出限制 RESPONSE_SCHEMA_REMOVED = 9004, // 问卷已删除 RESPONSE_DATA_DECRYPT_ERROR = 9005, // 问卷已删除 + RESPONSE_PAUSING = 9006, // 问卷已暂停 UPLOAD_FILE_ERROR = 5001, // 上传文件错误 } diff --git a/server/src/enums/index.ts b/server/src/enums/index.ts index 5dc930dc..06d4b0ba 100644 --- a/server/src/enums/index.ts +++ b/server/src/enums/index.ts @@ -1,14 +1,15 @@ // 状态类型 export enum RECORD_STATUS { - NEW = 'new', // 新建 + NEW = 'new', // 新建 | 未发布 + PUBLISHED = 'published', // 发布 EDITING = 'editing', // 编辑 + FINISHED = 'finished', // 已结束 + REMOVED = 'removed', +} + +export const enum RECORD_SUB_STATUS { + DEFAULT = '', // 默认 PAUSING = 'pausing', // 暂停 - PUBLISHED = 'published', // 发布 - REMOVED = 'removed', // 删除 - FORCE_REMOVED = 'forceRemoved', // 从回收站删除 - COMPUTING = 'computing', // 计算中 - FINISHED = 'finished', // 已完成 - ERROR = 'error', // 错误 } // 历史类型 diff --git a/server/src/enums/surveySessionStatus.ts b/server/src/enums/surveySessionStatus.ts new file mode 100644 index 00000000..8f644be1 --- /dev/null +++ b/server/src/enums/surveySessionStatus.ts @@ -0,0 +1,4 @@ +export enum SESSION_STATUS { + ACTIVATED = 'activated', + DEACTIVATED = 'deactivated', +} diff --git a/server/src/guards/session.guard.ts b/server/src/guards/session.guard.ts index 4a4a51f5..9de14393 100644 --- a/server/src/guards/session.guard.ts +++ b/server/src/guards/session.guard.ts @@ -24,6 +24,7 @@ export class SessionGuard implements CanActivate { } const sessionInfo = await this.sessionService.findOne(sessionId); request.sessionInfo = sessionInfo; + request.surveyId = sessionInfo.surveyId; return true; } } diff --git a/server/src/interfaces/survey.ts b/server/src/interfaces/survey.ts index dfb35131..b7368e26 100644 --- a/server/src/interfaces/survey.ts +++ b/server/src/interfaces/survey.ts @@ -119,7 +119,7 @@ export enum MemberType { } export interface BaseConf { - begTime: string; + beginTime: string; endTime: string; answerBegTime: string; answerEndTime: string; diff --git a/server/src/logger/index.ts b/server/src/logger/index.ts index f6fd8b17..c1fab057 100644 --- a/server/src/logger/index.ts +++ b/server/src/logger/index.ts @@ -1,12 +1,15 @@ import * as log4js from 'log4js'; import moment from 'moment'; -import { Injectable, Scope } from '@nestjs/common'; +import { Injectable, Scope, Inject } from '@nestjs/common'; +import { CONTEXT, RequestContext } from '@nestjs/microservices'; + const log4jsLogger = log4js.getLogger(); @Injectable({ scope: Scope.REQUEST }) export class Logger { private static inited = false; - private traceId: string; + + constructor(@Inject(CONTEXT) private readonly ctx: RequestContext) {} static init(config: { filename: string }) { if (Logger.inited) { @@ -37,16 +40,14 @@ export class Logger { const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS'); const level = options?.level; const dltag = options?.dltag ? `${options.dltag}||` : ''; - const traceIdStr = this.traceId ? `traceid=${this.traceId}||` : ''; + const traceIdStr = this.ctx?.['traceId'] + ? `traceid=${this.ctx?.['traceId']}||` + : ''; return log4jsLogger[level]( `[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`, ); } - setTraceId(traceId: string) { - this.traceId = traceId; - } - info(message, options?: { dltag?: string }) { return this._log(message, { ...options, level: 'info' }); } diff --git a/server/src/logger/util.ts b/server/src/logger/util.ts index 4ffc3c1b..1f25d257 100644 --- a/server/src/logger/util.ts +++ b/server/src/logger/util.ts @@ -20,7 +20,9 @@ export const genTraceId = ({ ip }) => { } else { ipArr = ip .split('.') - .map((item) => parseInt(item).toString(16).padStart(2, '0')); + .map((item) => + item ? parseInt(item).toString(16).padStart(2, '0') : '', + ); } return `${ipArr.join('')}${Date.now().toString()}${getCountStr()}${process.pid.toString().slice(-5)}`; diff --git a/server/src/main.ts b/server/src/main.ts index d764600e..95a0a907 100644 --- a/server/src/main.ts +++ b/server/src/main.ts @@ -1,6 +1,7 @@ import { NestFactory } from '@nestjs/core'; import { AppModule } from './app.module'; import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger'; +import 'scripts/run-report'; async function bootstrap() { const PORT = process.env.PORT || 3000; diff --git a/server/src/middlewares/logRequest.middleware.ts b/server/src/middlewares/logRequest.middleware.ts index 20d4b676..752ab9be 100644 --- a/server/src/middlewares/logRequest.middleware.ts +++ b/server/src/middlewares/logRequest.middleware.ts @@ -12,7 +12,7 @@ export class LogRequestMiddleware implements NestMiddleware { const userAgent = req.get('user-agent') || ''; const startTime = Date.now(); const traceId = genTraceId({ ip }); - this.logger.setTraceId(traceId); + req['traceId'] = traceId; const query = JSON.stringify(req.query); const body = JSON.stringify(req.body); this.logger.info( diff --git a/server/src/models/base.entity.ts b/server/src/models/base.entity.ts index 6f30e043..43216835 100644 --- a/server/src/models/base.entity.ts +++ b/server/src/models/base.entity.ts @@ -1,43 +1,13 @@ -import { Column, ObjectIdColumn, BeforeInsert, BeforeUpdate } from 'typeorm'; +import { ObjectIdColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm'; import { ObjectId } from 'mongodb'; -import { RECORD_STATUS } from '../enums'; export class BaseEntity { @ObjectIdColumn() _id: ObjectId; - @Column() - curStatus: { - status: RECORD_STATUS; - date: number; - }; + @CreateDateColumn({ type: 'timestamp', precision: 3 }) + createdAt: Date; - @Column() - statusList: Array<{ - status: RECORD_STATUS; - date: number; - }>; - - @Column() - createDate: number; - - @Column() - updateDate: number; - - @BeforeInsert() - initDefaultInfo() { - const now = Date.now(); - if (!this.curStatus) { - const curStatus = { status: RECORD_STATUS.NEW, date: now }; - this.curStatus = curStatus; - this.statusList = [curStatus]; - } - this.createDate = now; - this.updateDate = now; - } - - @BeforeUpdate() - onUpdate() { - this.updateDate = Date.now(); - } + @UpdateDateColumn({ type: 'timestamp', precision: 3 }) + updatedAt: Date; } diff --git a/server/src/models/collaborator.entity.ts b/server/src/models/collaborator.entity.ts index 9e17f02b..2d4e35e0 100644 --- a/server/src/models/collaborator.entity.ts +++ b/server/src/models/collaborator.entity.ts @@ -11,4 +11,16 @@ export class Collaborator extends BaseEntity { @Column('jsonb') permissions: Array; + + @Column() + creator: string; + + @Column() + creatorId: string; + + @Column() + operator: string; + + @Column() + operatorId: string; } diff --git a/server/src/models/downloadTask.entity.ts b/server/src/models/downloadTask.entity.ts index a1e371bc..a65bcc12 100644 --- a/server/src/models/downloadTask.entity.ts +++ b/server/src/models/downloadTask.entity.ts @@ -1,5 +1,6 @@ import { Entity, Column } from 'typeorm'; import { BaseEntity } from './base.entity'; +import { DOWNLOAD_TASK_STATUS } from 'src/enums/downloadTaskStatus'; @Entity({ name: 'downloadTask' }) export class DownloadTask extends BaseEntity { @@ -35,4 +36,13 @@ export class DownloadTask extends BaseEntity { @Column() params: string; + + @Column() + isDeleted: boolean; + + @Column() + deletedAt: Date; + + @Column() + status: DOWNLOAD_TASK_STATUS; } diff --git a/server/src/models/messagePushingTask.entity.ts b/server/src/models/messagePushingTask.entity.ts index 90aad278..b23051e9 100644 --- a/server/src/models/messagePushingTask.entity.ts +++ b/server/src/models/messagePushingTask.entity.ts @@ -27,4 +27,16 @@ export class MessagePushingTask extends BaseEntity { @Column() ownerId: string; + + @Column() + isDeleted: boolean; + + @Column() + deletedAt: Date; + + @Column() + operator: string; + + @Column() + operatorId: string; } diff --git a/server/src/models/responseSchema.entity.ts b/server/src/models/responseSchema.entity.ts index 60ee6fe8..fd3cd8e4 100644 --- a/server/src/models/responseSchema.entity.ts +++ b/server/src/models/responseSchema.entity.ts @@ -1,6 +1,7 @@ import { Entity, Column } from 'typeorm'; import { SurveySchemaInterface } from '../interfaces/survey'; import { BaseEntity } from './base.entity'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums'; @Entity({ name: 'surveyPublish' }) export class ResponseSchema extends BaseEntity { @@ -15,4 +16,19 @@ export class ResponseSchema extends BaseEntity { @Column() pageId: string; + + @Column() + curStatus: { + status: RECORD_STATUS; + date: number; + }; + + @Column() + subStatus: { + status: RECORD_SUB_STATUS; + date: number; + }; + + @Column() + isDeleted: boolean; } diff --git a/server/src/models/session.entity.ts b/server/src/models/session.entity.ts index bee82cc1..6f67e92b 100644 --- a/server/src/models/session.entity.ts +++ b/server/src/models/session.entity.ts @@ -1,6 +1,7 @@ import { Entity, Column, Index, ObjectIdColumn } from 'typeorm'; import { ObjectId } from 'mongodb'; import { BaseEntity } from './base.entity'; +import { SESSION_STATUS } from 'src/enums/surveySessionStatus'; @Entity({ name: 'session' }) export class Session extends BaseEntity { @@ -15,4 +16,7 @@ export class Session extends BaseEntity { @Column() userId: string; + + @Column() + status: SESSION_STATUS; } diff --git a/server/src/models/surveyHistory.entity.ts b/server/src/models/surveyHistory.entity.ts index 5d7650e4..4d573c4b 100644 --- a/server/src/models/surveyHistory.entity.ts +++ b/server/src/models/surveyHistory.entity.ts @@ -19,4 +19,7 @@ export class SurveyHistory extends BaseEntity { username: string; _id: string; }; + + @Column('string') + sessionId: string; } diff --git a/server/src/models/surveyMeta.entity.ts b/server/src/models/surveyMeta.entity.ts index b7543b9b..3e303470 100644 --- a/server/src/models/surveyMeta.entity.ts +++ b/server/src/models/surveyMeta.entity.ts @@ -1,5 +1,6 @@ -import { Entity, Column } from 'typeorm'; +import { Entity, Column, BeforeInsert } from 'typeorm'; import { BaseEntity } from './base.entity'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums'; @Entity({ name: 'surveyMeta' }) export class SurveyMeta extends BaseEntity { @@ -18,6 +19,9 @@ export class SurveyMeta extends BaseEntity { @Column() creator: string; + @Column() + creatorId: string; + @Column() owner: string; @@ -32,4 +36,48 @@ export class SurveyMeta extends BaseEntity { @Column() workspaceId: string; + + @Column() + curStatus: { + status: RECORD_STATUS; + date: number; + }; + + @Column() + subStatus: { + status: RECORD_SUB_STATUS; + date: number; + }; + + @Column() + statusList: Array<{ + status: RECORD_STATUS | RECORD_SUB_STATUS; + date: number; + }>; + + @Column() + operator: string; + + @Column() + operatorId: string; + + @Column() + isDeleted: boolean; + + @Column() + deletedAt: Date; + + @BeforeInsert() + initDefaultInfo() { + const now = Date.now(); + if (!this.curStatus) { + const curStatus = { status: RECORD_STATUS.NEW, date: now }; + this.curStatus = curStatus; + this.statusList = [curStatus]; + } + if (!this.subStatus) { + const subStatus = { status: RECORD_SUB_STATUS.DEFAULT, date: now }; + this.subStatus = subStatus; + } + } } diff --git a/server/src/models/workspace.entity.ts b/server/src/models/workspace.entity.ts index a918d607..8afb38d0 100644 --- a/server/src/models/workspace.entity.ts +++ b/server/src/models/workspace.entity.ts @@ -3,12 +3,33 @@ import { BaseEntity } from './base.entity'; @Entity({ name: 'workspace' }) export class Workspace extends BaseEntity { + @Column() + creatorId: string; + + @Column() + creator: string; + @Column() ownerId: string; + @Column() + owner: string; + @Column() name: string; @Column() description: string; + + @Column() + operator: string; + + @Column() + operatorId: string; + + @Column() + isDeleted: boolean; + + @Column() + deletedAt: Date; } diff --git a/server/src/models/workspaceMember.entity.ts b/server/src/models/workspaceMember.entity.ts index 72a6d186..3117138a 100644 --- a/server/src/models/workspaceMember.entity.ts +++ b/server/src/models/workspaceMember.entity.ts @@ -11,4 +11,16 @@ export class WorkspaceMember extends BaseEntity { @Column() role: string; + + @Column() + creator: string; + + @Column() + creatorId: string; + + @Column() + operator: string; + + @Column() + operatorId: string; } diff --git a/server/src/modules/auth/__test/user.service.spec.ts b/server/src/modules/auth/__test/user.service.spec.ts index e0cdfc19..1a9cc4b9 100644 --- a/server/src/modules/auth/__test/user.service.spec.ts +++ b/server/src/modules/auth/__test/user.service.spec.ts @@ -5,7 +5,6 @@ import { UserService } from '../services/user.service'; import { User } from 'src/models/user.entity'; import { HttpException } from 'src/exceptions/httpException'; import { hash256 } from 'src/utils/hash256'; -import { RECORD_STATUS } from 'src/enums'; import { ObjectId } from 'mongodb'; describe('UserService', () => { @@ -145,7 +144,6 @@ describe('UserService', () => { expect(userRepository.findOne).toHaveBeenCalledWith({ where: { username: username, - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, }); expect(user).toEqual(userInfo); @@ -163,7 +161,6 @@ describe('UserService', () => { expect(findOneSpy).toHaveBeenCalledWith({ where: { username: username, - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, }); expect(user).toBe(null); @@ -184,7 +181,6 @@ describe('UserService', () => { expect(userRepository.findOne).toHaveBeenCalledWith({ where: { _id: new ObjectId(id), - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, }); expect(user).toEqual(userInfo); @@ -202,7 +198,6 @@ describe('UserService', () => { expect(findOneSpy).toHaveBeenCalledWith({ where: { _id: new ObjectId(id), - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, }); expect(user).toBe(null); @@ -228,7 +223,6 @@ describe('UserService', () => { expect(userRepository.find).toHaveBeenCalledWith({ where: { username: new RegExp(username), - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, skip: 0, take: 10, @@ -263,7 +257,6 @@ describe('UserService', () => { _id: { $in: idList.map((id) => new ObjectId(id)), }, - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, select: ['_id', 'username', 'createDate'], }); diff --git a/server/src/modules/auth/services/user.service.ts b/server/src/modules/auth/services/user.service.ts index 03e4f497..0cbea97e 100644 --- a/server/src/modules/auth/services/user.service.ts +++ b/server/src/modules/auth/services/user.service.ts @@ -5,7 +5,6 @@ import { User } from 'src/models/user.entity'; import { HttpException } from 'src/exceptions/httpException'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { hash256 } from 'src/utils/hash256'; -import { RECORD_STATUS } from 'src/enums'; import { ObjectId } from 'mongodb'; @Injectable() @@ -53,9 +52,6 @@ export class UserService { const user = await this.userRepository.findOne({ where: { username: username, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, }); @@ -66,9 +62,6 @@ export class UserService { const user = await this.userRepository.findOne({ where: { _id: new ObjectId(id), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, }); @@ -79,13 +72,10 @@ export class UserService { const list = await this.userRepository.find({ where: { username: new RegExp(username), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, skip, take, - select: ['_id', 'username', 'createDate'], + select: ['_id', 'username', 'createdAt'], }); return list; } @@ -96,11 +86,8 @@ export class UserService { _id: { $in: idList.map((item) => new ObjectId(item)), }, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, - select: ['_id', 'username', 'createDate'], + select: ['_id', 'username', 'createdAt'], }); return list; } diff --git a/server/src/modules/message/__test/messagePushingTask.service.spec.ts b/server/src/modules/message/__test/messagePushingTask.service.spec.ts index 38d8852c..13acb78b 100644 --- a/server/src/modules/message/__test/messagePushingTask.service.spec.ts +++ b/server/src/modules/message/__test/messagePushingTask.service.spec.ts @@ -10,7 +10,6 @@ import { MessagePushingLogService } from '../services/messagePushingLog.service' import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto'; import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto'; -import { RECORD_STATUS } from 'src/enums'; import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing'; import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing'; import { MessagePushingTask } from 'src/models/messagePushingTask.entity'; @@ -121,7 +120,6 @@ describe('MessagePushingTaskService', () => { ownerId: mockOwnerId, surveys: { $all: [surveyId] }, triggerHook: hook, - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, }); }); @@ -146,7 +144,6 @@ describe('MessagePushingTaskService', () => { where: { ownerId: mockOwnerId, _id: new ObjectId(taskId), - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, }); }); @@ -161,10 +158,6 @@ describe('MessagePushingTaskService', () => { pushAddress: 'http://update.example.com', triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED, surveys: ['new survey id'], - curStatus: { - status: RECORD_STATUS.EDITING, - date: Date.now(), - }, }; const existingTask = new MessagePushingTask(); existingTask._id = new ObjectId(taskId); @@ -197,34 +190,26 @@ describe('MessagePushingTaskService', () => { const taskId = '65afc62904d5db18534c0f78'; const updateResult = { modifiedCount: 1 }; - const mockOwnerId = '66028642292c50f8b71a9eee'; + const mockOperatorId = '66028642292c50f8b71a9eee'; + const mockOperator = 'mockOperator'; jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult); const result = await service.remove({ id: taskId, - ownerId: mockOwnerId, + operatorId: mockOperatorId, + operator: mockOperator, }); expect(result).toEqual(updateResult); expect(repository.updateOne).toHaveBeenCalledWith( { - ownerId: mockOwnerId, + ownerId: mockOperatorId, _id: new ObjectId(taskId), - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, { $set: { - curStatus: { - status: RECORD_STATUS.REMOVED, - date: expect.any(Number), - }, - }, - $push: { - statusList: { - status: RECORD_STATUS.REMOVED, - date: expect.any(Number), - }, + isDeleted: true, }, }, ); diff --git a/server/src/modules/message/controllers/messagePushingTask.controller.ts b/server/src/modules/message/controllers/messagePushingTask.controller.ts index a3938b69..1406a405 100644 --- a/server/src/modules/message/controllers/messagePushingTask.controller.ts +++ b/server/src/modules/message/controllers/messagePushingTask.controller.ts @@ -150,8 +150,9 @@ export class MessagePushingTaskController { async remove(@Request() req, @Param('id') id: string) { const userId = req.user._id; const res = await this.messagePushingTaskService.remove({ - ownerId: userId, id, + operator: req.user.username, + operatorId: userId, }); return { code: 200, diff --git a/server/src/modules/message/dto/messagePushingTask.dto.ts b/server/src/modules/message/dto/messagePushingTask.dto.ts index 5144b178..91bf8e96 100644 --- a/server/src/modules/message/dto/messagePushingTask.dto.ts +++ b/server/src/modules/message/dto/messagePushingTask.dto.ts @@ -4,7 +4,6 @@ import { MESSAGE_PUSHING_TYPE, MESSAGE_PUSHING_HOOK, } from 'src/enums/messagePushing'; -import { RECORD_STATUS } from 'src/enums'; export class MessagePushingTaskDto { @ApiProperty({ description: '任务id' }) @@ -27,12 +26,6 @@ export class MessagePushingTaskDto { @ApiProperty({ description: '所有者' }) owner: string; - - @ApiProperty({ description: '任务状态', required: false }) - curStatus?: { - status: RECORD_STATUS; - date: number; - }; } export class CodeDto { diff --git a/server/src/modules/message/dto/updateMessagePushingTask.dto.ts b/server/src/modules/message/dto/updateMessagePushingTask.dto.ts index bf4eb43b..5560c6b4 100644 --- a/server/src/modules/message/dto/updateMessagePushingTask.dto.ts +++ b/server/src/modules/message/dto/updateMessagePushingTask.dto.ts @@ -1,5 +1,4 @@ import { ApiProperty } from '@nestjs/swagger'; -import { RECORD_STATUS } from 'src/enums'; import { MESSAGE_PUSHING_TYPE, MESSAGE_PUSHING_HOOK, @@ -20,10 +19,4 @@ export class UpdateMessagePushingTaskDto { @ApiProperty({ description: '绑定的问卷id', required: false }) surveys?: string[]; - - @ApiProperty({ description: '任务状态', required: false }) - curStatus?: { - status: RECORD_STATUS; - date: number; - }; } diff --git a/server/src/modules/message/services/messagePushingTask.service.ts b/server/src/modules/message/services/messagePushingTask.service.ts index 14aef5bf..6a5afd41 100644 --- a/server/src/modules/message/services/messagePushingTask.service.ts +++ b/server/src/modules/message/services/messagePushingTask.service.ts @@ -6,7 +6,6 @@ import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing'; import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto'; import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto'; import { ObjectId } from 'mongodb'; -import { RECORD_STATUS } from 'src/enums'; import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing'; import { MessagePushingLogService } from './messagePushingLog.service'; import { httpPost } from 'src/utils/request'; @@ -44,8 +43,8 @@ export class MessagePushingTaskService { ownerId?: string; }): Promise { const where: Record = { - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }; if (surveyId) { @@ -75,8 +74,8 @@ export class MessagePushingTaskService { where: { ownerId, _id: new ObjectId(id), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }, }); @@ -104,25 +103,25 @@ export class MessagePushingTaskService { return await this.messagePushingTaskRepository.save(updatedTask); } - async remove({ id, ownerId }: { id: string; ownerId: string }) { - const curStatus = { - status: RECORD_STATUS.REMOVED, - date: Date.now(), - }; + async remove({ + id, + operator, + operatorId, + }: { + id: string; + operator: string; + operatorId: string; + }) { return this.messagePushingTaskRepository.updateOne( { - ownerId, _id: new ObjectId(id), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, { $set: { - curStatus, - }, - $push: { - statusList: curStatus as never, + isDeleted: true, + operator, + operatorId, + deletedAt: new Date(), }, }, ); @@ -147,6 +146,9 @@ export class MessagePushingTaskService { $push: { surveys: surveyId as never, }, + $set: { + updatedAt: new Date(), + }, }, ); } diff --git a/server/src/modules/survey/__test/dataStatistic.controller.spec.ts b/server/src/modules/survey/__test/dataStatistic.controller.spec.ts index 8a0b3673..7b1649c7 100644 --- a/server/src/modules/survey/__test/dataStatistic.controller.spec.ts +++ b/server/src/modules/survey/__test/dataStatistic.controller.spec.ts @@ -233,7 +233,7 @@ describe('DataStatisticController', () => { }, }, baseConf: { - begTime: '2024-05-31 20:31:36', + beginTime: '2024-05-31 20:31:36', endTime: '2034-05-31 20:31:36', language: 'chinese', showVoteProcess: 'allow', diff --git a/server/src/modules/survey/__test/downloadTask.service.spec.ts b/server/src/modules/survey/__test/downloadTask.service.spec.ts index b26b9d20..14fe70a8 100644 --- a/server/src/modules/survey/__test/downloadTask.service.spec.ts +++ b/server/src/modules/survey/__test/downloadTask.service.spec.ts @@ -118,7 +118,6 @@ describe('DownloadTaskService', () => { expect(downloadTaskRepository.findAndCount).toHaveBeenCalledWith({ where: { creatorId: mockCreatorId, - 'curStatus.status': { $ne: RECORD_STATUS.REMOVED }, }, take: 10, skip: 0, diff --git a/server/src/modules/survey/__test/mockResponseSchema.ts b/server/src/modules/survey/__test/mockResponseSchema.ts index 3d0275c2..d308dfc7 100644 --- a/server/src/modules/survey/__test/mockResponseSchema.ts +++ b/server/src/modules/survey/__test/mockResponseSchema.ts @@ -32,7 +32,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = { }, }, baseConf: { - begTime: '2024-03-14 14:54:41', + beginTime: '2024-03-14 14:54:41', endTime: '2034-03-14 14:54:41', language: 'chinese', tLimit: 0, @@ -300,7 +300,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = { }, }, pageId: '65f29f3192862d6a9067ad1c', -} as ResponseSchema; +} as unknown as ResponseSchema; export const mockResponseSchema: ResponseSchema = { _id: new ObjectId('65b0d46e04d5db18534c0f7c'), @@ -331,7 +331,7 @@ export const mockResponseSchema: ResponseSchema = { }, }, baseConf: { - begTime: '2024-01-23 21:59:05', + beginTime: '2024-01-23 21:59:05', endTime: '2034-01-23 21:59:05', language: 'chinese', tLimit: 0, @@ -666,4 +666,4 @@ export const mockResponseSchema: ResponseSchema = { pageId: '65afc62904d5db18534c0f78', createDate: 1710340841289, updateDate: 1710340841289.0, -} as ResponseSchema; +} as unknown as ResponseSchema; diff --git a/server/src/modules/survey/__test/survey.controller.spec.ts b/server/src/modules/survey/__test/survey.controller.spec.ts index b9906bed..d141508f 100644 --- a/server/src/modules/survey/__test/survey.controller.spec.ts +++ b/server/src/modules/survey/__test/survey.controller.spec.ts @@ -172,7 +172,7 @@ describe('SurveyController', () => { bannerConfig: {}, }, baseConf: { - begTime: '2024-01-23 21:59:05', + beginTime: '2024-01-23 21:59:05', endTime: '2034-01-23 21:59:05', }, bottomConf: { logoImage: '/imgs/Logo.webp', logoImageWidth: '60%' }, diff --git a/server/src/modules/survey/__test/surveyHistory.service.spec.ts b/server/src/modules/survey/__test/surveyHistory.service.spec.ts index 78001c63..3ab1d2f5 100644 --- a/server/src/modules/survey/__test/surveyHistory.service.spec.ts +++ b/server/src/modules/survey/__test/surveyHistory.service.spec.ts @@ -42,7 +42,7 @@ describe('SurveyHistoryService', () => { msgContent: undefined, }, baseConf: { - begTime: '', + beginTime: '', endTime: '', answerBegTime: '', answerEndTime: '', diff --git a/server/src/modules/survey/__test/surveyMeta.controller.spec.ts b/server/src/modules/survey/__test/surveyMeta.controller.spec.ts index 5cd801eb..4d567850 100644 --- a/server/src/modules/survey/__test/surveyMeta.controller.spec.ts +++ b/server/src/modules/survey/__test/surveyMeta.controller.spec.ts @@ -121,6 +121,9 @@ describe('SurveyMetaController', () => { curStatus: { date: date, }, + subStatus: { + date: date, + }, surveyType: 'normal', }, ], @@ -138,14 +141,16 @@ describe('SurveyMetaController', () => { createDate: expect.stringMatching( /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, ), - updateDate: expect.stringMatching( - /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, - ), curStatus: expect.objectContaining({ date: expect.stringMatching( /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, ), }), + subStatus: expect.objectContaining({ + date: expect.stringMatching( + /^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/, + ), + }), surveyType: 'normal', }), ]), diff --git a/server/src/modules/survey/__test/surveyMeta.service.spec.ts b/server/src/modules/survey/__test/surveyMeta.service.spec.ts index 74b9e9e2..3e15ebd3 100644 --- a/server/src/modules/survey/__test/surveyMeta.service.spec.ts +++ b/server/src/modules/survey/__test/surveyMeta.service.spec.ts @@ -4,7 +4,7 @@ import { MongoRepository } from 'typeorm'; import { SurveyMeta } from 'src/models/surveyMeta.entity'; import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider'; import { PluginManager } from 'src/securityPlugin/pluginManager'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; import { getRepositoryToken } from '@nestjs/typeorm'; import { HttpException } from 'src/exceptions/httpException'; import { SurveyUtilPlugin } from 'src/securityPlugin/surveyUtilPlugin'; @@ -98,6 +98,10 @@ describe('SurveyMetaService', () => { it('should edit a survey meta and return it if in NEW or EDITING status', async () => { const survey = new SurveyMeta(); survey.curStatus = { status: RECORD_STATUS.PUBLISHED, date: Date.now() }; + survey.subStatus = { + status: RECORD_SUB_STATUS.DEFAULT, + date: Date.now(), + }; survey.statusList = []; jest.spyOn(surveyRepository, 'save').mockResolvedValue(survey); @@ -116,6 +120,10 @@ describe('SurveyMetaService', () => { // 准备假的SurveyMeta对象 const survey = new SurveyMeta(); survey.curStatus = { status: RECORD_STATUS.NEW, date: Date.now() }; + survey.subStatus = { + status: RECORD_SUB_STATUS.DEFAULT, + date: Date.now(), + }; survey.statusList = []; // 模拟save方法 @@ -126,7 +134,7 @@ describe('SurveyMetaService', () => { // 验证结果 expect(result).toBe(survey); - expect(survey.curStatus.status).toBe(RECORD_STATUS.REMOVED); + expect(survey.subStatus.status).toBe(RECORD_STATUS.REMOVED); expect(survey.statusList.length).toBe(1); expect(survey.statusList[0].status).toBe(RECORD_STATUS.REMOVED); expect(surveyRepository.save).toHaveBeenCalledTimes(1); @@ -136,7 +144,10 @@ describe('SurveyMetaService', () => { it('should throw exception when survey is already removed', async () => { // 准备假的SurveyMeta对象,其状态已设置为REMOVED const survey = new SurveyMeta(); - survey.curStatus = { status: RECORD_STATUS.REMOVED, date: Date.now() }; + survey.curStatus = { + status: RECORD_STATUS.REMOVED, + date: Date.now(), + }; // 调用要测试的方法并期待异常 await expect(service.deleteSurveyMeta(survey)).rejects.toThrow( @@ -193,6 +204,10 @@ describe('SurveyMetaService', () => { status: RECORD_STATUS.PUBLISHED, date: expect.any(Number), }, + subStatus: { + status: RECORD_SUB_STATUS.DEFAULT, + date: expect.any(Number), + }, } as unknown as SurveyMeta; jest.spyOn(surveyRepository, 'save').mockResolvedValue(savedSurveyMeta); diff --git a/server/src/modules/survey/controllers/collaborator.controller.ts b/server/src/modules/survey/controllers/collaborator.controller.ts index da1d5b5e..be6b5a6e 100644 --- a/server/src/modules/survey/controllers/collaborator.controller.ts +++ b/server/src/modules/survey/controllers/collaborator.controller.ts @@ -185,10 +185,14 @@ export class CollaboratorController { userIdList: newCollaboratorUserIdList, }); this.logger.info('batchDelete:' + JSON.stringify(delRes)); + const username = req.user.username; + const userId = req.user._id.toString(); if (Array.isArray(newCollaborator) && newCollaborator.length > 0) { const insertRes = await this.collaboratorService.batchCreate({ surveyId: value.surveyId, collaboratorList: newCollaborator, + creator: username, + creatorId: userId, }); this.logger.info(`${JSON.stringify(insertRes)}`); } @@ -198,6 +202,8 @@ export class CollaboratorController { this.collaboratorService.updateById({ collaboratorId: item._id, permissions: item.permissions, + operator: username, + operatorId: userId, }), ), ); diff --git a/server/src/modules/survey/controllers/downloadTask.controller.ts b/server/src/modules/survey/controllers/downloadTask.controller.ts index 8247acf7..4a83baab 100644 --- a/server/src/modules/survey/controllers/downloadTask.controller.ts +++ b/server/src/modules/survey/controllers/downloadTask.controller.ts @@ -98,7 +98,7 @@ export class DownloadTaskController { list: list.map((data) => { const item: Record = {}; item.taskId = data._id.toString(); - item.curStatus = data.curStatus; + item.status = data.status; item.filename = data.filename; item.url = data.url; const fmt = 'YYYY-MM-DD HH:mm:ss'; @@ -114,7 +114,7 @@ export class DownloadTaskController { } item.fileSize = `${size.toFixed()} ${units[unitIndex]}`; } - item.createDate = moment(Number(data.createDate)).format(fmt); + item.createdAt = moment(data.createdAt).format(fmt); return item; }), }, @@ -177,6 +177,8 @@ export class DownloadTaskController { const delRes = await this.downloadTaskService.deleteDownloadTask({ taskId, + operator: req.user.username, + operatorId: req.user._id.toString(), }); return { diff --git a/server/src/modules/survey/controllers/session.controller.ts b/server/src/modules/survey/controllers/session.controller.ts index f17dbaef..93f01528 100644 --- a/server/src/modules/survey/controllers/session.controller.ts +++ b/server/src/modules/survey/controllers/session.controller.ts @@ -67,9 +67,9 @@ export class SessionController { @Post('/seize') @HttpCode(200) - @UseGuards(SurveyGuard) - @UseGuards(SessionGuard) + @UseGuards(SessionGuard, SurveyGuard) @SetMetadata('sessionId', 'body.sessionId') + @SetMetadata('surveyId', 'surveyId') @SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE]) @UseGuards(Authentication) async seize( diff --git a/server/src/modules/survey/controllers/survey.controller.ts b/server/src/modules/survey/controllers/survey.controller.ts index 2a7bc4ee..98d32d89 100644 --- a/server/src/modules/survey/controllers/survey.controller.ts +++ b/server/src/modules/survey/controllers/survey.controller.ts @@ -144,7 +144,7 @@ export class SurveyController { if (latestEditingOne && latestEditingOne._id.toString() !== sessionId) { const curSession = await this.sessionService.findOne(sessionId); - if (curSession.createDate <= latestEditingOne.updateDate) { + if (curSession.createdAt <= latestEditingOne.updatedAt) { // 在当前用户打开之后,被其他页面保存过了 const isSameOperator = latestEditingOne.userId === req.user._id.toString(); @@ -194,8 +194,35 @@ export class SurveyController { async deleteSurvey(@Request() req) { const surveyMeta = req.surveyMeta; - await this.surveyMetaService.deleteSurveyMeta(surveyMeta); - await this.responseSchemaService.deleteResponseSchema({ + const delMetaRes = await this.surveyMetaService.deleteSurveyMeta({ + surveyId: surveyMeta._id.toString(), + operator: req.user.username, + operatorId: req.user._id.toString(), + }); + const delResponseRes = + await this.responseSchemaService.deleteResponseSchema({ + surveyPath: surveyMeta.surveyPath, + }); + + this.logger.info(JSON.stringify(delMetaRes)); + this.logger.info(JSON.stringify(delResponseRes)); + + return { + code: 200, + }; + } + + @HttpCode(200) + @Post('/pausingSurvey') + @UseGuards(SurveyGuard) + @SetMetadata('surveyId', 'body.surveyId') + @SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE]) + @UseGuards(Authentication) + async pausingSurvey(@Request() req) { + const surveyMeta = req.surveyMeta; + + await this.surveyMetaService.pausingSurveyMeta(surveyMeta); + await this.responseSchemaService.pausingResponseSchema({ surveyPath: surveyMeta.surveyPath, }); @@ -305,6 +332,12 @@ export class SurveyController { const username = req.user.username; const surveyId = value.surveyId; const surveyMeta = req.surveyMeta; + if (surveyMeta.isDeleted) { + throw new HttpException( + '问卷已删除,无法发布', + EXCEPTION_CODE.SURVEY_NOT_FOUND, + ); + } const surveyConf = await this.surveyConfService.getSurveyConfBySurveyId(surveyId); @@ -330,7 +363,8 @@ export class SurveyController { pageId: surveyId, }); - await this.surveyHistoryService.addHistory({ + // 添加发布历史可以异步添加 + this.surveyHistoryService.addHistory({ surveyId, schema: surveyConf.code, type: HISTORY_TYPE.PUBLISH_HIS, diff --git a/server/src/modules/survey/controllers/surveyHistory.controller.ts b/server/src/modules/survey/controllers/surveyHistory.controller.ts index 286b47f0..220cae4b 100644 --- a/server/src/modules/survey/controllers/surveyHistory.controller.ts +++ b/server/src/modules/survey/controllers/surveyHistory.controller.ts @@ -18,7 +18,7 @@ import { Logger } from 'src/logger'; import { HttpException } from 'src/exceptions/httpException'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; @ApiTags('survey') -@Controller('/api/surveyHisotry') +@Controller('/api/surveyHistory') export class SurveyHistoryController { constructor( private readonly surveyHistoryService: SurveyHistoryService, diff --git a/server/src/modules/survey/controllers/surveyMeta.controller.ts b/server/src/modules/survey/controllers/surveyMeta.controller.ts index ec50148e..5acf41d5 100644 --- a/server/src/modules/survey/controllers/surveyMeta.controller.ts +++ b/server/src/modules/survey/controllers/surveyMeta.controller.ts @@ -58,7 +58,11 @@ export class SurveyMetaController { survey.title = value.title; survey.remark = value.remark; - await this.surveyMetaService.editSurveyMeta(survey); + await this.surveyMetaService.editSurveyMeta({ + survey, + operator: req.user.username, + operatorId: req.user._id.toString(), + }); return { code: 200, @@ -127,9 +131,10 @@ export class SurveyMetaController { if (!item.surveyType) { item.surveyType = item.questionType || 'normal'; } - item.createDate = moment(item.createDate).format(fmt); - item.updateDate = moment(item.updateDate).format(fmt); + item.createdAt = moment(item.createdAt).format(fmt); item.curStatus.date = moment(item.curStatus.date).format(fmt); + item.subStatus.date = moment(item.subStatus.date).format(fmt); + item.updatedAt = moment(item.updatedAt).format(fmt); const surveyId = item._id.toString(); if (cooperSurveyIdMap[surveyId]) { item.isCollaborated = true; diff --git a/server/src/modules/survey/services/collaborator.service.ts b/server/src/modules/survey/services/collaborator.service.ts index cfd6c628..169c24f8 100644 --- a/server/src/modules/survey/services/collaborator.service.ts +++ b/server/src/modules/survey/services/collaborator.service.ts @@ -22,12 +22,17 @@ export class CollaboratorService { return this.collaboratorRepository.save(collaborator); } - async batchCreate({ surveyId, collaboratorList }) { + async batchCreate({ surveyId, collaboratorList, creator, creatorId }) { + const now = new Date(); const res = await this.collaboratorRepository.insertMany( collaboratorList.map((item) => { return { ...item, surveyId, + createdAt: now, + updatedAt: now, + creator, + creatorId, }; }), ); @@ -60,7 +65,13 @@ export class CollaboratorService { return info; } - async changeUserPermission({ userId, surveyId, permission }) { + async changeUserPermission({ + userId, + surveyId, + permission, + operator, + operatorId, + }) { const updateRes = await this.collaboratorRepository.updateOne( { surveyId, @@ -69,6 +80,9 @@ export class CollaboratorService { { $set: { permission, + operator, + operatorId, + updatedAt: new Date(), }, }, ); @@ -134,7 +148,7 @@ export class CollaboratorService { return delRes; } - updateById({ collaboratorId, permissions }) { + updateById({ collaboratorId, permissions, operator, operatorId }) { return this.collaboratorRepository.updateOne( { _id: new ObjectId(collaboratorId), @@ -142,6 +156,9 @@ export class CollaboratorService { { $set: { permissions, + operator, + operatorId, + updatedAt: new Date(), }, }, ); diff --git a/server/src/modules/survey/services/dataStatistic.service.ts b/server/src/modules/survey/services/dataStatistic.service.ts index 0551b432..fbb29ec5 100644 --- a/server/src/modules/survey/services/dataStatistic.service.ts +++ b/server/src/modules/survey/services/dataStatistic.service.ts @@ -34,8 +34,8 @@ export class DataStatisticService { const dataListMap = keyBy(dataList, 'field'); const where = { pageId: surveyId, - 'curStatus.status': { - $ne: 'removed', + isDeleted: { + $ne: true, }, }; const [surveyResponseList, total] = @@ -44,7 +44,7 @@ export class DataStatisticService { take: pageSize, skip: (pageNum - 1) * pageSize, order: { - createDate: -1, + createdAt: -1, }, }); @@ -90,10 +90,10 @@ export class DataStatisticService { } return { ...data, - diffTime: (submitedData.diffTime / 1000).toFixed(2), - createDate: moment(submitedData.createDate).format( - 'YYYY-MM-DD HH:mm:ss', - ), + diffTime: submitedData.diffTime + ? (submitedData.diffTime / 1000).toFixed(2) + : '0', + createdAt: moment(submitedData.createdAt).format('YYYY-MM-DD HH:mm:ss'), }; }); return { @@ -124,8 +124,8 @@ export class DataStatisticService { { $match: { pageId: surveyId, - 'curStatus.status': { - $ne: 'removed', + isDeleted: { + $ne: true, }, }, }, diff --git a/server/src/modules/survey/services/downloadTask.service.ts b/server/src/modules/survey/services/downloadTask.service.ts index 9dce616a..3808730d 100644 --- a/server/src/modules/survey/services/downloadTask.service.ts +++ b/server/src/modules/survey/services/downloadTask.service.ts @@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { MongoRepository } from 'typeorm'; import { ResponseSchema } from 'src/models/responseSchema.entity'; import { DownloadTask } from 'src/models/downloadTask.entity'; -import { RECORD_STATUS } from 'src/enums'; import { ObjectId } from 'mongodb'; import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service'; import { SurveyResponse } from 'src/models/surveyResponse.entity'; @@ -14,11 +13,12 @@ import { get } from 'lodash'; import { FileService } from 'src/modules/file/services/file.service'; import { Logger } from 'src/logger'; import moment from 'moment'; +import { DOWNLOAD_TASK_STATUS } from 'src/enums/downloadTaskStatus'; @Injectable() export class DownloadTaskService { - static taskList: Array = []; - static isExecuting: boolean = false; + private static taskList: Array = []; + private static isExecuting: boolean = false; constructor( @InjectRepository(DownloadTask) @@ -56,6 +56,7 @@ export class DownloadTaskService { title: responseSchema.title, }, filename, + status: DOWNLOAD_TASK_STATUS.WAITING, }); await this.downloadTaskRepository.save(downloadTask); return downloadTask._id.toString(); @@ -72,8 +73,8 @@ export class DownloadTaskService { }) { const where = { creatorId, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }; const [surveyDownloadList, total] = @@ -82,7 +83,7 @@ export class DownloadTaskService { take: pageSize, skip: (pageIndex - 1) * pageSize, order: { - createDate: -1, + createdAt: -1, }, }); return { @@ -103,24 +104,25 @@ export class DownloadTaskService { return null; } - async deleteDownloadTask({ taskId }: { taskId: string }) { - const curStatus = { - status: RECORD_STATUS.REMOVED, - date: Date.now(), - }; + async deleteDownloadTask({ + taskId, + operator, + operatorId, + }: { + taskId: string; + operator: string; + operatorId: string; + }) { return this.downloadTaskRepository.updateOne( { _id: new ObjectId(taskId), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, { $set: { - curStatus, - }, - $push: { - statusList: curStatus as never, + isDeleted: true, + operator, + operatorId, + deletedAt: new Date(), }, }, ); @@ -140,7 +142,7 @@ export class DownloadTaskService { const taskId = DownloadTaskService.taskList.shift(); this.logger.info(`handle taskId: ${taskId}`); const taskInfo = await this.getDownloadTaskById({ taskId }); - if (!taskInfo || taskInfo.curStatus.status === RECORD_STATUS.REMOVED) { + if (!taskInfo || taskInfo.isDeleted) { // 不存在或者已删除的,不处理 continue; } @@ -160,10 +162,8 @@ export class DownloadTaskService { }, { $set: { - curStatus: { - status: RECORD_STATUS.COMPUTING, - date: Date.now(), - }, + status: DOWNLOAD_TASK_STATUS.COMPUTING, + updatedAt: new Date(), }, }, ); @@ -176,9 +176,6 @@ export class DownloadTaskService { await this.responseSchemaService.getResponseSchemaByPageId(surveyId); const where = { pageId: surveyId, - 'curStatus.status': { - $ne: 'removed', - }, }; const total = await this.surveyResponseRepository.count(where); const pageSize = 200; @@ -205,9 +202,13 @@ export class DownloadTaskService { for (const headItem of listHead) { const field = headItem.field; const val = get(bodyItem, field, ''); - const $ = load(val); - const text = $.text(); - bodyData.push(text); + if (typeof val === 'string') { + const $ = load(val); + const text = $.text(); + bodyData.push(text); + } else { + bodyData.push(val); + } } xlsxBody.push(bodyData); } @@ -236,11 +237,6 @@ export class DownloadTaskService { filename: taskInfo.filename, }); - const curStatus = { - status: RECORD_STATUS.FINISHED, - date: Date.now(), - }; - // 更新计算结果 const updateFinishRes = await this.downloadTaskRepository.updateOne( { @@ -248,32 +244,24 @@ export class DownloadTaskService { }, { $set: { - curStatus, + status: DOWNLOAD_TASK_STATUS.SUCCEED, url, fileKey: key, fileSize: buffer.length, - }, - $push: { - statusList: curStatus as never, + updatedAt: new Date(), }, }, ); this.logger.info(JSON.stringify(updateFinishRes)); } catch (error) { - const curStatus = { - status: RECORD_STATUS.ERROR, - date: Date.now(), - }; await this.downloadTaskRepository.updateOne( { _id: taskInfo._id, }, { $set: { - curStatus, - }, - $push: { - statusList: curStatus as never, + status: DOWNLOAD_TASK_STATUS.FAILED, + updatedAt: new Date(), }, }, ); diff --git a/server/src/modules/survey/services/session.service.ts b/server/src/modules/survey/services/session.service.ts index e70fd3da..a0060d8e 100644 --- a/server/src/modules/survey/services/session.service.ts +++ b/server/src/modules/survey/services/session.service.ts @@ -3,7 +3,7 @@ import { InjectRepository } from '@nestjs/typeorm'; import { MongoRepository } from 'typeorm'; import { Session } from 'src/models/session.entity'; import { ObjectId } from 'mongodb'; -import { RECORD_STATUS } from 'src/enums'; +import { SESSION_STATUS } from 'src/enums/surveySessionStatus'; @Injectable() export class SessionService { @@ -16,6 +16,7 @@ export class SessionService { const session = this.sessionRepository.create({ surveyId, userId, + status: SESSION_STATUS.DEACTIVATED, }); return this.sessionRepository.save(session); } @@ -32,33 +33,20 @@ export class SessionService { return this.sessionRepository.findOne({ where: { surveyId, - 'curStatus.status': { - $ne: RECORD_STATUS.NEW, - }, + status: SESSION_STATUS.ACTIVATED, }, }); } updateSessionToEditing({ sessionId, surveyId }) { - const now = Date.now(); - const editingStatus = { - status: RECORD_STATUS.EDITING, - date: now, - }; - const newStatus = { - status: RECORD_STATUS.NEW, - date: now, - }; return Promise.all([ - this.sessionRepository.updateOne( + this.sessionRepository.update( { _id: new ObjectId(sessionId), }, { - $set: { - curStatus: editingStatus, - updateDate: now, - }, + status: SESSION_STATUS.ACTIVATED, + updatedAt: new Date(), }, ), this.sessionRepository.updateMany( @@ -70,8 +58,8 @@ export class SessionService { }, { $set: { - curStatus: newStatus, - updateDate: now, + status: SESSION_STATUS.DEACTIVATED, + updatedAt: new Date(), }, }, ), diff --git a/server/src/modules/survey/services/surveyHistory.service.ts b/server/src/modules/survey/services/surveyHistory.service.ts index 0b512faf..0eda2c8f 100644 --- a/server/src/modules/survey/services/surveyHistory.service.ts +++ b/server/src/modules/survey/services/surveyHistory.service.ts @@ -45,9 +45,9 @@ export class SurveyHistoryService { }, take: 100, order: { - createDate: -1, + createdAt: -1, }, - select: ['createDate', 'operator', 'type', '_id'], + select: ['createdAt', 'operator', 'type', '_id'], }); } } diff --git a/server/src/modules/survey/services/surveyMeta.service.ts b/server/src/modules/survey/services/surveyMeta.service.ts index b61106d7..71a160a8 100644 --- a/server/src/modules/survey/services/surveyMeta.service.ts +++ b/server/src/modules/survey/services/surveyMeta.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { MongoRepository, FindOptionsOrder } from 'typeorm'; import { SurveyMeta } from 'src/models/surveyMeta.entity'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; import { ObjectId } from 'mongodb'; import { HttpException } from 'src/exceptions/httpException'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; @@ -65,6 +65,7 @@ export class SurveyMetaService { surveyType: surveyType, surveyPath, creator: username, + creatorId: userId, owner: username, ownerId: userId, createMethod, @@ -75,41 +76,65 @@ export class SurveyMetaService { return await this.surveyRepository.save(newSurvey); } - async editSurveyMeta(survey: SurveyMeta) { - if ( - survey.curStatus.status !== RECORD_STATUS.NEW && - survey.curStatus.status !== RECORD_STATUS.EDITING - ) { - const newStatus = { - status: RECORD_STATUS.EDITING, - date: Date.now(), - }; - survey.curStatus = newStatus; - survey.statusList.push(newStatus); - } - return this.surveyRepository.save(survey); - } - - async deleteSurveyMeta(survey: SurveyMeta) { - if (survey.curStatus.status === RECORD_STATUS.REMOVED) { + async pausingSurveyMeta(survey: SurveyMeta) { + if (survey?.curStatus?.status === RECORD_STATUS.NEW) { throw new HttpException( - '问卷已删除,不能重复删除', + '问卷不能暂停', EXCEPTION_CODE.SURVEY_STATUS_TRANSFORM_ERROR, ); } - const newStatusInfo = { - status: RECORD_STATUS.REMOVED, + const subCurStatus = { + status: RECORD_SUB_STATUS.PAUSING, date: Date.now(), }; - survey.curStatus = newStatusInfo; + survey.subStatus = subCurStatus; if (Array.isArray(survey.statusList)) { - survey.statusList.push(newStatusInfo); + survey.statusList.push(subCurStatus); } else { - survey.statusList = [newStatusInfo]; + survey.statusList = [subCurStatus]; + } + return this.surveyRepository.save(survey); + } + + async editSurveyMeta({ + survey, + operator, + operatorId, + }: { + survey: SurveyMeta; + operator: string; + operatorId: string; + }) { + if (survey?.curStatus?.status !== RECORD_STATUS.EDITING) { + const newStatus = { + status: RECORD_STATUS.EDITING, + date: Date.now(), + }; + survey.curStatus = newStatus; + survey.statusList.push(newStatus); } + survey.updatedAt = new Date(); + survey.operator = operator; + survey.operatorId = operatorId; return this.surveyRepository.save(survey); } + async deleteSurveyMeta({ surveyId, operator, operatorId }) { + return this.surveyRepository.updateOne( + { + _id: new ObjectId(surveyId), + }, + { + $set: { + isDeleted: true, + operator, + operatorId, + deletedAt: new Date(), + }, + }, + ); + } + async getSurveyMetaList(condition: { pageNum: number; pageSize: number; @@ -125,14 +150,16 @@ export class SurveyMetaService { const skip = (pageNum - 1) * pageSize; try { const query: Record = Object.assign( - {}, { - 'curStatus.status': { - $ne: 'removed', + isDeleted: { + $ne: true, }, }, condition.filter, ); + if (condition.filter['curStatus.status']) { + query['subStatus.status'] = RECORD_SUB_STATUS.DEFAULT; + } if (workspaceId) { query.workspaceId = workspaceId; } else { @@ -160,9 +187,8 @@ export class SurveyMetaService { condition.order && Object.keys(condition.order).length > 0 ? (condition.order as FindOptionsOrder) : ({ - createDate: -1, + createdAt: -1, } as FindOptionsOrder); - const [data, count] = await this.surveyRepository.findAndCount({ where: query, skip, @@ -181,6 +207,10 @@ export class SurveyMetaService { date: Date.now(), }; surveyMeta.curStatus = curStatus; + surveyMeta.subStatus = { + status: RECORD_SUB_STATUS.DEFAULT, + date: Date.now(), + }; if (Array.isArray(surveyMeta.statusList)) { surveyMeta.statusList.push(curStatus); } else { @@ -192,8 +222,8 @@ export class SurveyMetaService { async countSurveyMetaByWorkspaceId({ workspaceId }) { const total = await this.surveyRepository.count({ workspaceId, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }); return total; diff --git a/server/src/modules/survey/template/surveyTemplate/templateBase.json b/server/src/modules/survey/template/surveyTemplate/templateBase.json index ecd4f419..bbe43911 100644 --- a/server/src/modules/survey/template/surveyTemplate/templateBase.json +++ b/server/src/modules/survey/template/surveyTemplate/templateBase.json @@ -25,11 +25,6 @@ "msg_9003": "您来晚了,已经满额!", "msg_9004": "提交失败!" }, - "jumpConfig": { - "type": "link", - "link": "", - "buttonText": "" - }, "link": "" }, "bottomConf": { @@ -37,7 +32,7 @@ "logoImageWidth": "60%" }, "baseConf": { - "begTime": "2024-01-01 00:00:00", + "beginTime": "2024-01-01 00:00:00", "endTime": "2034-01-01 00:00:00", "tLimit": 0, "language": "chinese", diff --git a/server/src/modules/survey/utils/index.ts b/server/src/modules/survey/utils/index.ts index a6a64ccd..286f0430 100644 --- a/server/src/modules/survey/utils/index.ts +++ b/server/src/modules/survey/utils/index.ts @@ -22,7 +22,7 @@ export async function getSchemaBySurveyType(surveyType: string) { } const code = Object.assign({}, templateBase, codeData); const nowMoment = moment(); - code.baseConf.begTime = nowMoment.format('YYYY-MM-DD HH:mm:ss'); + code.baseConf.beginTime = nowMoment.format('YYYY-MM-DD HH:mm:ss'); code.baseConf.endTime = nowMoment .add(10, 'years') .format('YYYY-MM-DD HH:mm:ss'); @@ -63,7 +63,7 @@ export function getListHeadByDataList(dataList) { type: QUESTION_TYPE.TEXT, }); listHead.push({ - field: 'createDate', + field: 'createdAt', title: '提交时间', type: QUESTION_TYPE.TEXT, }); diff --git a/server/src/modules/surveyResponse/__test/clientEncrypt.service.spec.ts b/server/src/modules/surveyResponse/__test/clientEncrypt.service.spec.ts index 9d2418c6..5dfc0919 100644 --- a/server/src/modules/surveyResponse/__test/clientEncrypt.service.spec.ts +++ b/server/src/modules/surveyResponse/__test/clientEncrypt.service.spec.ts @@ -3,7 +3,6 @@ import { MongoRepository } from 'typeorm'; import { ClientEncryptService } from '../services/clientEncrypt.service'; import { ClientEncrypt } from 'src/models/clientEncrypt.entity'; import { ENCRYPT_TYPE } from 'src/enums/encrypt'; -import { RECORD_STATUS } from 'src/enums'; import { ObjectId } from 'mongodb'; import { getRepositoryToken } from '@nestjs/typeorm'; @@ -88,9 +87,6 @@ describe('ClientEncryptService', () => { expect(repository.findOne).toHaveBeenCalledWith({ where: { _id: new ObjectId(id), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, }); expect(result).toEqual(encryptInfo); diff --git a/server/src/modules/surveyResponse/__test/mockResponseSchema.ts b/server/src/modules/surveyResponse/__test/mockResponseSchema.ts index a76a48df..3a9d544b 100644 --- a/server/src/modules/surveyResponse/__test/mockResponseSchema.ts +++ b/server/src/modules/surveyResponse/__test/mockResponseSchema.ts @@ -1,5 +1,5 @@ import { ResponseSchema } from 'src/models/responseSchema.entity'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; import { ObjectId } from 'mongodb'; export const mockResponseSchema: ResponseSchema = { @@ -8,6 +8,10 @@ export const mockResponseSchema: ResponseSchema = { status: RECORD_STATUS.PUBLISHED, date: 1710399368439, }, + subStatus: { + status: RECORD_SUB_STATUS.DEFAULT, + date: 1710399368439, + }, statusList: [ { status: RECORD_STATUS.PUBLISHED, @@ -32,7 +36,7 @@ export const mockResponseSchema: ResponseSchema = { }, }, baseConf: { - begTime: '2024-03-14 14:54:41', + beginTime: '2024-03-14 14:54:41', endTime: '2034-03-14 14:54:41', language: 'chinese', tLimit: 10, @@ -252,4 +256,4 @@ export const mockResponseSchema: ResponseSchema = { }, }, pageId: '65f29f3192862d6a9067ad1c', -} as ResponseSchema; +} as unknown as ResponseSchema; diff --git a/server/src/modules/surveyResponse/__test/responseSchema.controller.spec.ts b/server/src/modules/surveyResponse/__test/responseSchema.controller.spec.ts index 497580df..2119e66b 100644 --- a/server/src/modules/surveyResponse/__test/responseSchema.controller.spec.ts +++ b/server/src/modules/surveyResponse/__test/responseSchema.controller.spec.ts @@ -3,7 +3,7 @@ import { ResponseSchemaController } from '../controllers/responseSchema.controll import { ResponseSchemaService } from '../services/responseScheme.service'; import { HttpException } from 'src/exceptions/httpException'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; import { ResponseSchema } from 'src/models/responseSchema.entity'; import { Logger } from 'src/logger'; @@ -69,6 +69,7 @@ describe('ResponseSchemaController', () => { const mockResponseSchema = { surveyPath: 'testSurveyPath', curStatus: { status: RECORD_STATUS.PUBLISHED, date: Date.now() }, + subStatus: { status: RECORD_SUB_STATUS.DEFAULT, date: Date.now() }, } as ResponseSchema; jest @@ -97,7 +98,7 @@ describe('ResponseSchemaController', () => { jest .spyOn(responseSchemaService, 'getResponseSchemaByPath') .mockResolvedValue({ - curStatus: { status: RECORD_STATUS.REMOVED }, + subStatus: { status: RECORD_SUB_STATUS.REMOVED }, } as ResponseSchema); await expect(controller.getSchema(mockQueryInfo)).rejects.toThrow( @@ -125,6 +126,9 @@ describe('ResponseSchemaController', () => { curStatus: { status: 'published', }, + subStatus: { + status: '', + }, code: { baseConf: { passwordSwitch: true, @@ -149,6 +153,9 @@ describe('ResponseSchemaController', () => { curStatus: { status: 'published', }, + subStatus: { + status: '', + }, code: { baseConf: { passwordSwitch: true, @@ -172,6 +179,9 @@ describe('ResponseSchemaController', () => { curStatus: { status: 'published', }, + subStatus: { + status: '', + }, code: { baseConf: { passwordSwitch: true, @@ -200,6 +210,9 @@ describe('ResponseSchemaController', () => { curStatus: { status: 'published', }, + subStatus: { + status: '', + }, code: { baseConf: { passwordSwitch: true, @@ -228,6 +241,9 @@ describe('ResponseSchemaController', () => { curStatus: { status: 'published', }, + subStatus: { + status: '', + }, code: { baseConf: { passwordSwitch: true, diff --git a/server/src/modules/surveyResponse/__test/surveyResponse.controller.spec.ts b/server/src/modules/surveyResponse/__test/surveyResponse.controller.spec.ts index 82632bbe..e62a1253 100644 --- a/server/src/modules/surveyResponse/__test/surveyResponse.controller.spec.ts +++ b/server/src/modules/surveyResponse/__test/surveyResponse.controller.spec.ts @@ -18,7 +18,7 @@ import { HttpException } from 'src/exceptions/httpException'; import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException'; import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; import { SurveyResponse } from 'src/models/surveyResponse.entity'; import { Logger } from 'src/logger'; import { ResponseSchema } from 'src/models/responseSchema.entity'; @@ -334,6 +334,9 @@ describe('SurveyResponseController', () => { curStatus: { status: RECORD_STATUS.PUBLISHED, }, + subStatus: { + status: RECORD_SUB_STATUS.DEFAULT, + }, code: { baseConf: { passwordSwitch: true, diff --git a/server/src/modules/surveyResponse/__test/surveyResponse.service.spec.ts b/server/src/modules/surveyResponse/__test/surveyResponse.service.spec.ts index f91df266..bf6837c5 100644 --- a/server/src/modules/surveyResponse/__test/surveyResponse.service.spec.ts +++ b/server/src/modules/surveyResponse/__test/surveyResponse.service.spec.ts @@ -77,7 +77,7 @@ describe('SurveyResponseService', () => { expect(surveyResponseRepository.count).toHaveBeenCalledWith({ where: { surveyPath, - 'curStatus.status': { $ne: 'removed' }, + 'subStatus.status': { $ne: 'removed' }, }, }); }); diff --git a/server/src/modules/surveyResponse/controllers/responseSchema.controller.ts b/server/src/modules/surveyResponse/controllers/responseSchema.controller.ts index b6070c22..9f2ce285 100644 --- a/server/src/modules/surveyResponse/controllers/responseSchema.controller.ts +++ b/server/src/modules/surveyResponse/controllers/responseSchema.controller.ts @@ -10,7 +10,7 @@ import { import { ResponseSchemaService } from '../services/responseScheme.service'; import { HttpException } from 'src/exceptions/httpException'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_SUB_STATUS } from 'src/enums'; import { ApiTags } from '@nestjs/swagger'; import Joi from 'joi'; import { Logger } from 'src/logger'; @@ -44,16 +44,20 @@ export class ResponseSchemaController { await this.responseSchemaService.getResponseSchemaByPath( queryInfo.surveyPath, ); - if ( - !responseSchema || - responseSchema.curStatus.status === RECORD_STATUS.REMOVED - ) { + if (!responseSchema || responseSchema.isDeleted) { throw new HttpException( - '问卷已删除', + '问卷不存在或已删除', EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED, ); } + if (responseSchema.subStatus.status === RECORD_SUB_STATUS.PAUSING) { + throw new HttpException( + '该问卷已暂停回收', + EXCEPTION_CODE.RESPONSE_PAUSING, + ); + } + // 去掉C端的敏感字段 if (responseSchema.code?.baseConf) { responseSchema.code.baseConf.password = null; @@ -82,7 +86,7 @@ export class ResponseSchemaController { // 问卷信息 const schema = await this.responseSchemaService.getResponseSchemaByPath(surveyPath); - if (!schema || schema.curStatus.status === 'removed') { + if (!schema || schema.isDeleted) { throw new SurveyNotFoundException('该问卷不存在,无法提交'); } diff --git a/server/src/modules/surveyResponse/controllers/surveyResponse.controller.ts b/server/src/modules/surveyResponse/controllers/surveyResponse.controller.ts index f40aa9c0..18621967 100644 --- a/server/src/modules/surveyResponse/controllers/surveyResponse.controller.ts +++ b/server/src/modules/surveyResponse/controllers/surveyResponse.controller.ts @@ -5,6 +5,7 @@ import { checkSign } from 'src/utils/checkSign'; import { ENCRYPT_TYPE } from 'src/enums/encrypt'; import { EXCEPTION_CODE } from 'src/enums/exceptionCode'; import { getPushingData } from 'src/utils/messagePushing'; +import { RECORD_SUB_STATUS } from 'src/enums'; import { ResponseSchemaService } from '../services/responseScheme.service'; import { SurveyResponseService } from '../services/surveyResponse.service'; @@ -82,9 +83,15 @@ export class SurveyResponseController { // 查询schema const responseSchema = await this.responseSchemaService.getResponseSchemaByPath(surveyPath); - if (!responseSchema || responseSchema.curStatus.status === 'removed') { + if (!responseSchema || responseSchema.isDeleted) { throw new SurveyNotFoundException('该问卷不存在,无法提交'); } + if (responseSchema?.subStatus?.status === RECORD_SUB_STATUS.PAUSING) { + throw new HttpException( + '该问卷已暂停,无法提交', + EXCEPTION_CODE.RESPONSE_PAUSING, + ); + } // 白名单的verifyId校验 const baseConf = responseSchema.code.baseConf; @@ -132,12 +139,12 @@ export class SurveyResponseController { const now = Date.now(); // 提交时间限制 - const begTime = responseSchema.code?.baseConf?.begTime || 0; + const beginTime = responseSchema.code?.baseConf?.beginTime || 0; const endTime = responseSchema?.code?.baseConf?.endTime || 0; - if (begTime && endTime) { - const begTimeStamp = new Date(begTime).getTime(); + if (beginTime && endTime) { + const beginTimeStamp = new Date(beginTime).getTime(); const endTimeStamp = new Date(endTime).getTime(); - if (now < begTimeStamp || now > endTimeStamp) { + if (now < beginTimeStamp || now > endTimeStamp) { throw new HttpException( '不在答题有效期内', EXCEPTION_CODE.RESPONSE_CURRENT_TIME_NOT_ALLOW, diff --git a/server/src/modules/surveyResponse/services/clientEncrypt.service.ts b/server/src/modules/surveyResponse/services/clientEncrypt.service.ts index 7d204d99..e679bda8 100644 --- a/server/src/modules/surveyResponse/services/clientEncrypt.service.ts +++ b/server/src/modules/surveyResponse/services/clientEncrypt.service.ts @@ -4,7 +4,6 @@ import { MongoRepository } from 'typeorm'; import { ClientEncrypt } from 'src/models/clientEncrypt.entity'; import { ENCRYPT_TYPE } from 'src/enums/encrypt'; import { ObjectId } from 'mongodb'; -import { RECORD_STATUS } from 'src/enums'; @Injectable() export class ClientEncryptService { @@ -38,26 +37,13 @@ export class ClientEncryptService { return this.clientEncryptRepository.findOne({ where: { _id: new ObjectId(id), - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, }); } deleteEncryptInfo(id: string) { - return this.clientEncryptRepository.updateOne( - { - _id: new ObjectId(id), - }, - { - $set: { - curStatus: { - status: RECORD_STATUS.REMOVED, - date: Date.now(), - }, - }, - }, - ); + return this.clientEncryptRepository.deleteOne({ + _id: new ObjectId(id), + }); } } diff --git a/server/src/modules/surveyResponse/services/counter.service.ts b/server/src/modules/surveyResponse/services/counter.service.ts index 0b72ab82..d6581e49 100644 --- a/server/src/modules/surveyResponse/services/counter.service.ts +++ b/server/src/modules/surveyResponse/services/counter.service.ts @@ -33,6 +33,7 @@ export class CounterService { surveyPath, type, data, + updatedAt: new Date(), }, }, { diff --git a/server/src/modules/surveyResponse/services/responseScheme.service.ts b/server/src/modules/surveyResponse/services/responseScheme.service.ts index 701192bf..b1dc793c 100644 --- a/server/src/modules/surveyResponse/services/responseScheme.service.ts +++ b/server/src/modules/surveyResponse/services/responseScheme.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { MongoRepository } from 'typeorm'; import { ResponseSchema } from 'src/models/responseSchema.entity'; -import { RECORD_STATUS } from 'src/enums'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; @Injectable() export class ResponseSchemaService { @@ -23,19 +23,27 @@ export class ResponseSchemaService { status: RECORD_STATUS.PUBLISHED, date: Date.now(), }; + clientSurvey.subStatus = { + status: RECORD_SUB_STATUS.DEFAULT, + date: Date.now(), + }; return this.responseSchemaRepository.save(clientSurvey); } else { const curStatus = { status: RECORD_STATUS.PUBLISHED, date: Date.now(), }; + const subStatus = { + status: RECORD_SUB_STATUS.DEFAULT, + date: Date.now(), + }; const newClientSurvey = this.responseSchemaRepository.create({ title, surveyPath, code, pageId, curStatus, - statusList: [curStatus], + subStatus, }); return this.responseSchemaRepository.save(newClientSurvey); } @@ -53,22 +61,32 @@ export class ResponseSchemaService { }); } - async deleteResponseSchema({ surveyPath }) { + async pausingResponseSchema({ surveyPath }) { const responseSchema = await this.responseSchemaRepository.findOne({ where: { surveyPath }, }); if (responseSchema) { - const newStatus = { - status: RECORD_STATUS.PUBLISHED, + const subStatus = { + status: RECORD_SUB_STATUS.PAUSING, date: Date.now(), }; - responseSchema.curStatus = newStatus; - if (Array.isArray(responseSchema.statusList)) { - responseSchema.statusList.push(newStatus); - } else { - responseSchema.statusList = [newStatus]; - } + responseSchema.subStatus = subStatus; + responseSchema.curStatus.status = RECORD_STATUS.PUBLISHED; return this.responseSchemaRepository.save(responseSchema); } } + + async deleteResponseSchema({ surveyPath }) { + return this.responseSchemaRepository.updateOne( + { + surveyPath, + }, + { + $set: { + isDeleted: true, + updatedAt: new Date(), + }, + }, + ); + } } diff --git a/server/src/modules/surveyResponse/services/surveyResponse.service.ts b/server/src/modules/surveyResponse/services/surveyResponse.service.ts index 78d79333..fefca601 100644 --- a/server/src/modules/surveyResponse/services/surveyResponse.service.ts +++ b/server/src/modules/surveyResponse/services/surveyResponse.service.ts @@ -2,7 +2,7 @@ import { Injectable } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { MongoRepository } from 'typeorm'; import { SurveyResponse } from 'src/models/surveyResponse.entity'; -import { RECORD_STATUS } from 'src/enums'; + @Injectable() export class SurveyResponseService { constructor( @@ -39,9 +39,6 @@ export class SurveyResponseService { const data = await this.surveyResponseRepository.find({ where: { surveyPath, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, }); return (data || []).length; diff --git a/server/src/modules/upgrade/controllers/upgrade.controller.ts b/server/src/modules/upgrade/controllers/upgrade.controller.ts new file mode 100644 index 00000000..b2c6f4f6 --- /dev/null +++ b/server/src/modules/upgrade/controllers/upgrade.controller.ts @@ -0,0 +1,21 @@ +import { Controller, Get, HttpCode, Request } from '@nestjs/common'; +import { ApiTags } from '@nestjs/swagger'; +import { UpgradeService } from '../services/upgrade.service'; + +@ApiTags('survey') +@Controller('/api/upgrade') +export class UpgradeController { + constructor(private readonly upgradeService: UpgradeService) {} + + @Get('/upgradeFeatureStatus') + @HttpCode(200) + async upgradeSubStatus(@Request() req) { + this.upgradeService.upgradeFeatureStatus(); + return { + code: 200, + data: { + traceId: req.traceId, + }, + }; + } +} diff --git a/server/src/modules/upgrade/services/upgrade.service.ts b/server/src/modules/upgrade/services/upgrade.service.ts new file mode 100644 index 00000000..0364932b --- /dev/null +++ b/server/src/modules/upgrade/services/upgrade.service.ts @@ -0,0 +1,210 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { MongoRepository } from 'typeorm'; +import { SurveyMeta } from 'src/models/surveyMeta.entity'; +import { ResponseSchema } from 'src/models/responseSchema.entity'; +import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums'; +import { Workspace } from 'src/models/workspace.entity'; +import { Collaborator } from 'src/models/collaborator.entity'; +import { Counter } from 'src/models/counter.entity'; +import { DownloadTask } from 'src/models/downloadTask.entity'; +import { MessagePushingLog } from 'src/models/messagePushingLog.entity'; +import { MessagePushingTask } from 'src/models/messagePushingTask.entity'; +import { Session } from 'src/models/session.entity'; +import { SurveyConf } from 'src/models/surveyConf.entity'; +import { User } from 'src/models/user.entity'; +import { WorkspaceMember } from 'src/models/workspaceMember.entity'; +import { SESSION_STATUS } from 'src/enums/surveySessionStatus'; +import { Logger } from 'src/logger'; + +@Injectable() +export class UpgradeService { + constructor( + private readonly logger: Logger, + @InjectRepository(Collaborator) + private readonly collaboratorRepository: MongoRepository, + @InjectRepository(Counter) + private readonly counterRepository: MongoRepository, + @InjectRepository(DownloadTask) + private readonly downloadTaskRepository: MongoRepository, + @InjectRepository(MessagePushingLog) + private readonly messagePushingLogRepository: MongoRepository, + @InjectRepository(MessagePushingTask) + private readonly messagePushingTaskRepository: MongoRepository, + @InjectRepository(ResponseSchema) + private readonly responseSchemaRepository: MongoRepository, + @InjectRepository(Session) + private readonly sessionRepository: MongoRepository, + @InjectRepository(SurveyConf) + private readonly surveyConfRepository: MongoRepository, + @InjectRepository(SurveyMeta) + private readonly surveyMetaRepository: MongoRepository, + @InjectRepository(User) + private readonly userRepository: MongoRepository, + @InjectRepository(Workspace) + private readonly workspaceRepository: MongoRepository, + @InjectRepository(WorkspaceMember) + private readonly workspaceMemberRepository: MongoRepository, + ) {} + + async upgradeFeatureStatus() { + const repositories = [ + this.collaboratorRepository, + this.counterRepository, + this.downloadTaskRepository, + this.messagePushingLogRepository, + this.messagePushingTaskRepository, + this.responseSchemaRepository, + this.sessionRepository, + this.surveyConfRepository, + this.surveyMetaRepository, + this.userRepository, + this.workspaceRepository, + this.workspaceMemberRepository, + ]; + const handleCreatedAtAndUpdatedAt = (doc) => { + if (!doc.createdAt) { + if (doc.createDate) { + doc.createdAt = new Date(doc.createDate); + delete doc.createDate; + } else { + doc.createdAt = new Date(); + } + } + if (!doc.updatedAt) { + if (doc.updateDate) { + doc.updatedAt = new Date(doc.updateDate); + delete doc.updateDate; + } else { + doc.updatedAt = new Date(); + } + } + }; + + const handleDelStatus = (doc) => { + // 已删除的字段升级 + if (doc?.curStatus?.status === 'removed') { + delete doc.curStatus; + doc.isDeleted = true; + doc.deletedAt = new Date(doc.updatedAt); + } + }; + + const handleSubStatus = (doc) => { + // 编辑中字段升级 + if ( + !doc?.subStatus && + (doc?.curStatus?.status == RECORD_STATUS.PUBLISHED || + doc?.curStatus?.status == RECORD_STATUS.NEW || + doc?.curStatus?.status === RECORD_STATUS.EDITING) + ) { + const subStatus = { + status: RECORD_SUB_STATUS.DEFAULT, + date: doc.curStatus.date, + }; + doc.subStatus = subStatus; + } + }; + + const handleBegTime = (doc) => { + if (!doc?.baseConf?.beginTime && doc?.baseConf?.begTime) { + doc.baseConf.beginTime = doc.baseConf.begTime; + delete doc.baseConf.begTime; + } + }; + + const handleSessionStatus = (doc) => { + if (!doc.status && doc.curStatus) { + if (doc?.curStatus?.id && doc?.curStatus?.id === 'editing') { + doc.status = SESSION_STATUS.ACTIVATED; + } else { + doc.status = SESSION_STATUS.DEACTIVATED; + } + delete doc.curStatus; + } + }; + + const handleCreatorId = async (doc) => { + if (!doc.ownerId && doc.owner) { + const userInfo = await this.userRepository.findOne({ + where: { + username: doc.owner, + }, + }); + if (userInfo && userInfo._id) { + doc.ownerId = userInfo._id.toString(); + } + } + if (doc.ownerId && doc.owner && !doc.creatorId) { + doc.creatorId = doc.ownerId; + doc.creator = doc.owner; + } + }; + + const save = async ({ doc, repository }) => { + const entity = repository.create(doc); + await repository.updateOne( + { + _id: entity._id, + }, + { + $set: entity, + }, + ); + // this.logger.info(JSON.stringify(updateRes)); + }; + this.logger.info(`upgrading...`); + for (const repository of repositories) { + const name = + typeof repository.target === 'function' + ? repository.target.name + : typeof repository.target === 'string' + ? repository.target + : ''; + + const cursor = repository.createCursor(); + this.logger.info(`upgrading ${name}`); + while (await cursor.hasNext()) { + try { + const doc = await cursor.next(); + // 把createDate和updateDate升级成createdAt和updatedAt + handleCreatedAtAndUpdatedAt(doc); + if ( + repository === this.surveyMetaRepository || + repository === this.responseSchemaRepository + ) { + // 新增subStatus字段 + handleSubStatus(doc); + } + if ( + repository === this.surveyMetaRepository || + repository === this.downloadTaskRepository || + repository === this.messagePushingTaskRepository || + repository === this.workspaceRepository || + repository === this.responseSchemaRepository + ) { + // 新增isDeleted等相关字段 + handleDelStatus(doc); + } + // 同步sessionStatus到新定义的字段 + if (repository === this.sessionRepository) { + handleSessionStatus(doc); + } + // 同步begTime,更新成beginTime + if (repository === this.surveyConfRepository) { + handleBegTime(doc); + } + // 同步ownerId到creatorId + if (repository === this.surveyMetaRepository) { + await handleCreatorId(doc); + } + await save({ repository, doc }); + } catch (error) { + this.logger.error(`upgrade ${name} error ${error.message}`); + } + } + this.logger.info(`finish upgrade ${name}`); + } + this.logger.info(`upgrad finished...`); + } +} diff --git a/server/src/modules/upgrade/upgrade.module.ts b/server/src/modules/upgrade/upgrade.module.ts new file mode 100644 index 00000000..37f36e08 --- /dev/null +++ b/server/src/modules/upgrade/upgrade.module.ts @@ -0,0 +1,51 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { ConfigModule } from '@nestjs/config'; +import { UpgradeService } from './services/upgrade.service'; + +import { Collaborator } from 'src/models/collaborator.entity'; +import { Counter } from 'src/models/counter.entity'; +import { DownloadTask } from 'src/models/downloadTask.entity'; +import { MessagePushingLog } from 'src/models/messagePushingLog.entity'; +import { MessagePushingTask } from 'src/models/messagePushingTask.entity'; +import { ResponseSchema } from 'src/models/responseSchema.entity'; +import { Session } from 'src/models/session.entity'; +import { SurveyConf } from 'src/models/surveyConf.entity'; +import { SurveyHistory } from 'src/models/surveyHistory.entity'; +import { SurveyMeta } from 'src/models/surveyMeta.entity'; +import { SurveyResponse } from 'src/models/surveyResponse.entity'; +import { User } from 'src/models/user.entity'; +import { Workspace } from 'src/models/workspace.entity'; +import { WorkspaceMember } from 'src/models/workspaceMember.entity'; + +import { UpgradeController } from './controllers/upgrade.controller'; +import { AuthModule } from '../auth/auth.module'; + +import { Logger } from 'src/logger'; + +@Module({ + imports: [ + TypeOrmModule.forFeature([ + Collaborator, + Counter, + DownloadTask, + MessagePushingLog, + MessagePushingTask, + ResponseSchema, + Session, + SurveyConf, + SurveyHistory, + SurveyMeta, + SurveyResponse, + User, + Workspace, + WorkspaceMember, + ]), + ConfigModule, + AuthModule, + ], + controllers: [UpgradeController], + providers: [UpgradeService, Logger], + exports: [UpgradeService], +}) +export class UpgradeModule {} diff --git a/server/src/modules/workspace/_test/workspace.controller.spec.ts b/server/src/modules/workspace/_test/workspace.controller.spec.ts index 9431b6ce..ac5a893d 100644 --- a/server/src/modules/workspace/_test/workspace.controller.spec.ts +++ b/server/src/modules/workspace/_test/workspace.controller.spec.ts @@ -242,11 +242,15 @@ describe('WorkspaceController', () => { describe('getWorkspaceAndMember', () => { it('should return a list of workspaces and members for the user', async () => { - const req = { user: { _id: new ObjectId() } }; - - const workspaceId = new ObjectId(); const userId = new ObjectId(); - const memberList = [{ workspaceId, userId: userId }]; + jest.spyOn(userService, 'getUserListByIds').mockResolvedValue([ + { + _id: userId, + }, + ] as Array); + const req = { user: { _id: userId } }; + const workspaceId = new ObjectId(); + const memberList = [{ workspaceId, userId }]; const workspaces = [{ _id: workspaceId, name: 'Test Workspace' }]; const userList = [{ _id: userId, username: 'Test User' }]; diff --git a/server/src/modules/workspace/controllers/workspace.controller.ts b/server/src/modules/workspace/controllers/workspace.controller.ts index 7279c13f..276577f5 100644 --- a/server/src/modules/workspace/controllers/workspace.controller.ts +++ b/server/src/modules/workspace/controllers/workspace.controller.ts @@ -100,10 +100,12 @@ export class WorkspaceController { } } const userId = req.user._id.toString(); + const username = req.user.username; // 插入空间表 const retWorkspace = await this.workspaceService.create({ name: value.name, description: value.description, + owner: username, ownerId: userId, }); const workspaceId = retWorkspace._id.toString(); @@ -117,6 +119,8 @@ export class WorkspaceController { await this.workspaceMemberService.batchCreate({ workspaceId, members: value.members, + creator: username, + creatorId: userId, }); } return { @@ -206,7 +210,7 @@ export class WorkspaceController { const ownerInfo = userInfoMap?.[item.ownerId] || {}; return { ...item, - createDate: moment(item.createDate).format('YYYY-MM-DD HH:mm:ss'), + createdAt: moment(item.createdAt).format('YYYY-MM-DD HH:mm:ss'), owner: ownerInfo.username, currentUserId: curWorkspaceInfo.userId, currentUserRole: curWorkspaceInfo.role, @@ -268,13 +272,24 @@ export class WorkspaceController { @UseGuards(WorkspaceGuard) @SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE]) @SetMetadata('workspaceId', 'params.id') - async update(@Param('id') id: string, @Body() workspace: CreateWorkspaceDto) { + async update( + @Param('id') id: string, + @Body() workspace: CreateWorkspaceDto, + @Request() req, + ) { const members = workspace.members; if (!Array.isArray(members) || members.length === 0) { throw new HttpException('成员不能为空', EXCEPTION_CODE.PARAMETER_ERROR); } delete workspace.members; - const updateRes = await this.workspaceService.update(id, workspace); + const operator = req.user.username, + operatorId = req.user._id.toString(); + const updateRes = await this.workspaceService.update({ + id, + workspace, + operator, + operatorId, + }); this.logger.info(`updateRes: ${JSON.stringify(updateRes)}`); const { newMembers, adminMembers, userMembers } = splitMembers(members); if ( @@ -320,14 +335,20 @@ export class WorkspaceController { this.workspaceMemberService.batchCreate({ workspaceId: id, members: newMembers, + creator: operator, + creatorId: operatorId, }), this.workspaceMemberService.batchUpdate({ idList: adminMembers, role: WORKSPACE_ROLE.ADMIN, + operator, + operatorId, }), this.workspaceMemberService.batchUpdate({ idList: userMembers, role: WORKSPACE_ROLE.USER, + operator, + operatorId, }), ]); this.logger.info(`updateRes: ${JSON.stringify(res)}`); @@ -341,8 +362,13 @@ export class WorkspaceController { @UseGuards(WorkspaceGuard) @SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE]) @SetMetadata('workspaceId', 'params.id') - async delete(@Param('id') id: string) { - const res = await this.workspaceService.delete(id); + async delete(@Param('id') id: string, @Request() req) { + const operator = req.user.username, + operatorId = req.user._id.toString(); + const res = await this.workspaceService.delete(id, { + operator, + operatorId, + }); this.logger.info(`res: ${JSON.stringify(res)}`); return { code: 200, diff --git a/server/src/modules/workspace/controllers/workspaceMember.controller.ts b/server/src/modules/workspace/controllers/workspaceMember.controller.ts index 5dab6caa..6eac9668 100644 --- a/server/src/modules/workspace/controllers/workspaceMember.controller.ts +++ b/server/src/modules/workspace/controllers/workspaceMember.controller.ts @@ -77,7 +77,10 @@ export class WorkspaceMemberController { @Post('updateRole') @SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_MEMBER]) @SetMetadata('workspaceId', 'body.workspaceId') - async updateRole(@Body() updateDto: UpdateWorkspaceMemberDto) { + async updateRole( + @Body() updateDto: UpdateWorkspaceMemberDto, + @Request() req, + ) { const { error, value } = UpdateWorkspaceMemberDto.validate(updateDto); if (error) { throw new HttpException( @@ -85,10 +88,14 @@ export class WorkspaceMemberController { EXCEPTION_CODE.PARAMETER_ERROR, ); } + const operator = req.user.username, + operatorId = req.user._id.toString(); const updateRes = await this.workspaceMemberService.updateRole({ role: value.role, workspaceId: value.workspaceId, userId: value.userId, + operator, + operatorId, }); return { code: 200, diff --git a/server/src/modules/workspace/services/workspace.service.ts b/server/src/modules/workspace/services/workspace.service.ts index fb097a7a..b5444540 100644 --- a/server/src/modules/workspace/services/workspace.service.ts +++ b/server/src/modules/workspace/services/workspace.service.ts @@ -6,7 +6,6 @@ import { Workspace } from 'src/models/workspace.entity'; import { SurveyMeta } from 'src/models/surveyMeta.entity'; import { ObjectId } from 'mongodb'; -import { RECORD_STATUS } from 'src/enums'; interface FindAllByIdWithPaginationParams { workspaceIdList: string[]; @@ -31,10 +30,13 @@ export class WorkspaceService { async create(workspace: { name: string; description: string; + owner: string; ownerId: string; }): Promise { const newWorkspace = this.workspaceRepository.create({ ...workspace, + creatorId: workspace.ownerId, + creator: workspace.owner, }); return this.workspaceRepository.save(newWorkspace); } @@ -56,8 +58,8 @@ export class WorkspaceService { _id: { $in: workspaceIdList.map((item) => new ObjectId(item)), }, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }; @@ -68,11 +70,11 @@ export class WorkspaceService { }, select: [ '_id', - 'curStatus', 'name', 'description', 'ownerId', - 'createDate', + 'creatorId', + 'createdAt', ], }); } @@ -91,8 +93,8 @@ export class WorkspaceService { _id: { $in: workspaceIdList.map((m) => new ObjectId(m)), }, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }; if (name) { @@ -103,31 +105,40 @@ export class WorkspaceService { skip, take: limit, order: { - createDate: -1, + createdAt: -1, }, }); return { list: data, count }; } - update(id: string, workspace: Partial) { + update({ + id, + workspace, + operator, + operatorId, + }: { + id: string; + workspace: Partial; + operator: string; + operatorId: string; + }) { + workspace.updatedAt = new Date(); + workspace.operator = operator; + workspace.operatorId = operatorId; return this.workspaceRepository.update(id, workspace); } - async delete(id: string) { - const newStatus = { - status: RECORD_STATUS.REMOVED, - date: Date.now(), - }; + async delete(id: string, { operator, operatorId }) { const workspaceRes = await this.workspaceRepository.updateOne( { _id: new ObjectId(id), }, { $set: { - curStatus: newStatus, - }, - $push: { - statusList: newStatus as never, + isDeleted: true, + deletedAt: new Date(), + operator, + operatorId, }, }, ); @@ -137,10 +148,10 @@ export class WorkspaceService { }, { $set: { - curStatus: newStatus, - }, - $push: { - statusList: newStatus as never, + isDeleted: true, + deletedAt: new Date(), + operator, + operatorId, }, }, ); @@ -155,8 +166,8 @@ export class WorkspaceService { return await this.workspaceRepository.find({ where: { ownerId: userId, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, + isDeleted: { + $ne: true, }, }, order: { @@ -168,7 +179,7 @@ export class WorkspaceService { 'name', 'description', 'ownerId', - 'createDate', + 'createdAt', ], }); } diff --git a/server/src/modules/workspace/services/workspaceMember.service.ts b/server/src/modules/workspace/services/workspaceMember.service.ts index 2f711963..45c41dfe 100644 --- a/server/src/modules/workspace/services/workspaceMember.service.ts +++ b/server/src/modules/workspace/services/workspaceMember.service.ts @@ -3,7 +3,6 @@ import { InjectRepository } from '@nestjs/typeorm'; import { MongoRepository } from 'typeorm'; import { WorkspaceMember } from 'src/models/workspaceMember.entity'; import { ObjectId } from 'mongodb'; -import { RECORD_STATUS } from 'src/enums'; @Injectable() export class WorkspaceMemberService { @@ -24,25 +23,44 @@ export class WorkspaceMemberService { async batchCreate({ workspaceId, members, + creator, + creatorId, }: { workspaceId: string; members: Array<{ userId: string; role: string }>; + creator: string; + creatorId: string; }) { if (members.length === 0) { return { insertedCount: 0, }; } + const now = new Date(); const dataToInsert = members.map((item) => { return { ...item, workspaceId, + createdAt: now, + updatedAt: now, + creator, + creatorId, }; }); return this.workspaceMemberRepository.insertMany(dataToInsert); } - async batchUpdate({ idList, role }: { idList: Array; role: string }) { + async batchUpdate({ + idList, + role, + operator, + operatorId, + }: { + idList: Array; + role: string; + operator: string; + operatorId: string; + }) { if (idList.length === 0) { return { modifiedCount: 0, @@ -57,6 +75,9 @@ export class WorkspaceMemberService { { $set: { role, + operator, + operatorId, + updatedAt: new Date(), }, }, ); @@ -94,11 +115,8 @@ export class WorkspaceMemberService { return this.workspaceMemberRepository.find({ where: { workspaceId, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }, - select: ['_id', 'createDate', 'curStatus', 'role', 'userId'], + select: ['_id', 'createdAt', 'curStatus', 'role', 'userId'], }); } @@ -111,7 +129,7 @@ export class WorkspaceMemberService { }); } - async updateRole({ workspaceId, userId, role }) { + async updateRole({ workspaceId, userId, role, operator, operatorId }) { return this.workspaceMemberRepository.updateOne( { workspaceId, @@ -120,6 +138,9 @@ export class WorkspaceMemberService { { $set: { role, + operator, + operatorId, + updatedAt: new Date(), }, }, ); @@ -135,9 +156,6 @@ export class WorkspaceMemberService { async countByWorkspaceId({ workspaceId }) { return this.workspaceMemberRepository.count({ workspaceId, - 'curStatus.status': { - $ne: RECORD_STATUS.REMOVED, - }, }); } diff --git a/server/src/utils/surveyUtil.ts b/server/src/utils/surveyUtil.ts index a67cf157..3056555f 100644 --- a/server/src/utils/surveyUtil.ts +++ b/server/src/utils/surveyUtil.ts @@ -20,6 +20,7 @@ export function getFilter(filterList: Array) { 'remark', 'surveyType', 'curStatus.status', + 'subStatus.status', ]; return filterList.reduce( (preItem, curItem) => { @@ -61,7 +62,7 @@ export function getFilter(filterList: Array) { } export function getOrder(order: Array) { - const allowOrderFields = ['createDate', 'updateDate', 'curStatus.date']; + const allowOrderFields = ['createdAt', 'updatedAt', 'curStatus.date']; const orderList = order.filter((orderItem) => allowOrderFields.includes(orderItem.field), diff --git a/web/package.json b/web/package.json index 883a8bed..f9060096 100644 --- a/web/package.json +++ b/web/package.json @@ -1,7 +1,7 @@ { - "name": "web", - "version": "0.1.0", - "private": true, + "name": "xiaoju-survey-web", + "version": "1.3.0", + "description": "XIAOJUSURVEY的web端,包含B端和C端应用", "type": "module", "scripts": { "serve": "npm run dev", @@ -41,6 +41,7 @@ "@iconify-json/ep": "^1.1.15", "@rushstack/eslint-patch": "^1.10.2", "@tsconfig/node20": "^20.1.2", + "@types/fs-extra": "^11.0.4", "@types/lodash-es": "^4.17.12", "@types/node": "^20.11.19", "@types/qrcode": "^1.5.5", @@ -52,6 +53,7 @@ "@vue/tsconfig": "^0.5.1", "eslint": "^8.49.0", "eslint-plugin-vue": "^9.17.0", + "fs-extra": "^11.2.0", "husky": "^9.0.11", "npm-run-all2": "^6.1.1", "prettier": "^3.0.3", diff --git a/web/report.ts b/web/report.ts new file mode 100644 index 00000000..edb43840 --- /dev/null +++ b/web/report.ts @@ -0,0 +1,47 @@ +import fs from 'fs-extra' + +const fsa = fs.promises + +process.env.XIAOJU_SURVEY_REPORT = 'true' + +const readData = async (pkg: string) => { + const id = new Date().getTime().toString() + try { + if (!fs.existsSync(pkg)) { + return { + type: 'web', + name: '', + version: '', + description: '', + id, + msg: '文件不存在' + } + } + const data = await fsa.readFile(pkg, 'utf8').catch((e) => e) + const { name, version, description } = JSON.parse(data) + return { type: 'web', name, version, description, id } + } catch (error) { + return error + } +} + +const report = async () => { + if (!process.env.XIAOJU_SURVEY_REPORT) { + return + } + + const res = await readData('./package.json') + + // 上报 + fetch && + fetch('https://xiaojusurveysrc.didi.cn/reportSourceData', { + method: 'POST', + headers: { + Accept: 'application/json, */*', + 'Content-Type': 'application/json' + }, + body: JSON.stringify(res) + }).catch(() => {}) +} + +report() diff --git a/web/src/management/api/analysis.js b/web/src/management/api/analysis.js index 5f211572..a5930322 100644 --- a/web/src/management/api/analysis.js +++ b/web/src/management/api/analysis.js @@ -16,3 +16,4 @@ export const getStatisticList = (data) => { } }) } + diff --git a/web/src/management/api/survey.js b/web/src/management/api/survey.js index 1203b0d7..1666320a 100644 --- a/web/src/management/api/survey.js +++ b/web/src/management/api/survey.js @@ -35,7 +35,7 @@ export const createSurvey = (data) => { } export const getSurveyHistory = ({ surveyId, historyType }) => { - return axios.get('/surveyHisotry/getList', { + return axios.get('/surveyHistory/getList', { params: { surveyId, historyType @@ -53,6 +53,16 @@ export const updateSurvey = (data) => { return axios.post('/survey/updateMeta', data) } +export const pausingSurvey= (surveyId) => { + return axios.post('/survey/pausingSurvey', { + surveyId + }) +} + +export const upgradeSubStatus = () => { + return axios.get('/upgrade/subStatus') +} + export const getSessionId = ({ surveyId }) => { return axios.post('/session/create', { surveyId }) } diff --git a/web/src/management/config/listConfig.js b/web/src/management/config/listConfig.js index ca9578c1..890fb2c8 100644 --- a/web/src/management/config/listConfig.js +++ b/web/src/management/config/listConfig.js @@ -27,9 +27,9 @@ export const spaceListConfig = { key: 'owner', width: 150 }, - createDate: { + createdAt: { title: '创建时间', - key: 'createDate', + key: 'createdAt', minWidth: 200 } } @@ -64,14 +64,14 @@ export const fieldConfig = { key: 'owner', width: 140 }, - updateDate: { + updatedAt: { title: '更新时间', - key: 'curStatus.date', + key: 'updatedAt', minWidth: 200 }, - createDate: { + createdAt: { title: '创建时间', - key: 'createDate', + key: 'createdAt', minWidth: 200 } } @@ -103,14 +103,37 @@ export const noDownloadTaskConfig = { img: '/imgs/icons/list-empty.webp' } +export const curStatus = { + new: { + value: 'new', + label: '未发布' + }, + published: { + value: 'published', + label: '已发布' + }, + editing: { + label: '修改中', + value: 'editing' + }, +} + +// 子状态 +export const subStatus = { + pausing: { + label: '暂停中', + value: 'pausing' + } +} + export const statusMaps = { - new: '未发布', - editing: '修改中', - published: '已发布', - removed: '已删除', - pausing: '' + ...Object.fromEntries(Object.keys(curStatus).map(key => ([key, curStatus[key].label]))), + ...Object.fromEntries(Object.keys(subStatus).map(key => ([key, subStatus[key].label]))) } +export const curStatusKey = 'curStatus.status'; +export const subStatusKey = 'subStatus.status'; + // 问卷类型 export const surveyTypeSelect = { label: '问卷类型', @@ -151,29 +174,21 @@ export const curStatusSelect = { value: '', label: '全部状态' }, - { - value: 'new', - label: '未发布' - }, - { - value: 'published', - label: '已发布' - }, - { - value: 'editing', - label: '修改中' - } + curStatus.new, + curStatus.published, + curStatus.editing, + subStatus.pausing ], default: '' } export const selectOptionsDict = Object.freeze({ surveyType: surveyTypeSelect, - 'curStatus.status': curStatusSelect + status: curStatusSelect, }) export const buttonOptionsDict = Object.freeze({ - 'curStatus.date': { + 'updatedAt': { label: '更新时间', icons: [ { @@ -194,7 +209,7 @@ export const buttonOptionsDict = Object.freeze({ } ] }, - createDate: { + createdAt: { label: '创建时间', icons: [ { diff --git a/web/src/management/pages/analysis/components/DataTable.vue b/web/src/management/pages/analysis/components/DataTable.vue index a1a71f06..437315a9 100644 --- a/web/src/management/pages/analysis/components/DataTable.vue +++ b/web/src/management/pages/analysis/components/DataTable.vue @@ -80,7 +80,7 @@ const popoverContent = ref('') const getContent = (content) => { if (Array.isArray(content)) { - return content.map((item) => getContent(item)).join(',') + return content.map(item => getContent(item)).join(','); } if (content === null || content === undefined) { return '' diff --git a/web/src/management/pages/analysis/pages/DataTablePage.vue b/web/src/management/pages/analysis/pages/DataTablePage.vue index 4d438988..750cee34 100644 --- a/web/src/management/pages/analysis/pages/DataTablePage.vue +++ b/web/src/management/pages/analysis/pages/DataTablePage.vue @@ -194,7 +194,7 @@ const checkIsTaskFinished = (taskId) => { const run = () => { getDownloadTask(taskId).then((res) => { if (res.code === 200 && res.data) { - const status = res.data.curStatus.status + const status = res.data.status if (status === 'new' || status === 'computing') { setTimeout(() => { run() diff --git a/web/src/management/pages/download/components/TaskList.vue b/web/src/management/pages/download/components/TaskList.vue index b0a9eebc..4e199061 100644 --- a/web/src/management/pages/download/components/TaskList.vue +++ b/web/src/management/pages/download/components/TaskList.vue @@ -26,7 +26,7 @@