feat: 修改状态相关功能 (#433)
Co-authored-by: luchunhui <luchunhui@didiglobal.com>
This commit is contained in:
parent
0b4e1fa13b
commit
1125e2ae39
3
.gitignore
vendored
3
.gitignore
vendored
@ -25,7 +25,10 @@ pnpm-debug.log*
|
|||||||
*.sw?
|
*.sw?
|
||||||
|
|
||||||
.history
|
.history
|
||||||
|
|
||||||
components.d.ts
|
components.d.ts
|
||||||
|
|
||||||
# 默认的上传文件夹
|
# 默认的上传文件夹
|
||||||
userUpload
|
userUpload
|
||||||
|
exportfile
|
||||||
|
yarn.lock
|
22
DATA_COLLECTION.md
Normal file
22
DATA_COLLECTION.md
Normal file
@ -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%E9%87%87%E9%9B%86%E5%A3%B0%E6%98%8E) for information on configuring this functionality.
|
@ -144,9 +144,9 @@ npm run local
|
|||||||
|
|
||||||
#### 1、配置数据库
|
#### 1、配置数据库
|
||||||
|
|
||||||
> 项目使用 MongoDB,需要提前准备,请查看[如何拥有 MongoDB 指南](./数据库#安装)
|
> 项目使用 MongoDB,需要提前准备,请查看[如何拥有 MongoDB 指南](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E6%95%B0%E6%8D%AE%E5%BA%93#%E5%AE%89%E8%A3%85)
|
||||||
|
|
||||||
配置数据库信息,查看[MongoDB 配置](./数据库)。
|
配置数据库信息,查看[MongoDB 配置](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E6%95%B0%E6%8D%AE%E5%BA%93)。
|
||||||
|
|
||||||
#### 2、安装依赖
|
#### 2、安装依赖
|
||||||
|
|
||||||
|
@ -145,7 +145,7 @@ npm run local
|
|||||||
|
|
||||||
#### 1.Configure Database
|
#### 1.Configure Database
|
||||||
|
|
||||||
> The project uses MongoDB: [MongoDB Guide](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E6%95%B0%E6%8D%AE%E5%BA%93#%E5%AE%89%E8%A3%85)
|
> The project uses MongoDB: [MongoDB Guide](https://xiaojusurvey.didi.cn/docs/next/document/%E6%A6%82%E8%BF%B0/%E6%95%B0%E6%8D%AE%E5%BA%93)
|
||||||
|
|
||||||
Configure the database, check MongoDB configuration.
|
Configure the database, check MongoDB configuration.
|
||||||
|
|
||||||
|
@ -15,7 +15,7 @@ services:
|
|||||||
- xiaoju-survey
|
- xiaoju-survey
|
||||||
|
|
||||||
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
|
container_name: xiaoju-survey
|
||||||
restart: always
|
restart: always
|
||||||
ports:
|
ports:
|
||||||
|
@ -52,6 +52,9 @@ http {
|
|||||||
proxy_pass http://127.0.0.1:3000;
|
proxy_pass http://127.0.0.1:3000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
location /exportfile {
|
||||||
|
proxy_pass http://127.0.0.1:3000;
|
||||||
|
}
|
||||||
# 静态文件的默认存储文件夹
|
# 静态文件的默认存储文件夹
|
||||||
# 文件夹的配置在 server/src/modules/file/config/index.ts SERVER_LOCAL_CONFIG.FILE_KEY_PREFIX
|
# 文件夹的配置在 server/src/modules/file/config/index.ts SERVER_LOCAL_CONFIG.FILE_KEY_PREFIX
|
||||||
location /userUpload {
|
location /userUpload {
|
||||||
|
@ -1,7 +1,13 @@
|
|||||||
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
|
XIAOJU_SURVEY_MONGO_DB_NAME=xiaojuSurvey
|
||||||
XIAOJU_SURVEY_MONGO_URL=mongodb://localhost:27017
|
XIAOJU_SURVEY_MONGO_URL=
|
||||||
XIAOJU_SURVEY_MONGO_AUTH_SOURCE=admin
|
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_RESPONSE_AES_ENCRYPT_SECRET_KEY=dataAesEncryptSecretKey
|
||||||
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
|
XIAOJU_SURVEY_HTTP_DATA_ENCRYPT_TYPE=rsa
|
||||||
|
@ -0,0 +1,3 @@
|
|||||||
|
XIAOJU_SURVEY_REPORT=true
|
||||||
|
XIAOJU_SURVEY_MONGO_URL=mongodb://127.0.0.1:27017
|
||||||
|
|
5
server/.gitignore
vendored
5
server/.gitignore
vendored
@ -13,6 +13,7 @@ pnpm-debug.log*
|
|||||||
yarn-debug.log*
|
yarn-debug.log*
|
||||||
yarn-error.log*
|
yarn-error.log*
|
||||||
lerna-debug.log*
|
lerna-debug.log*
|
||||||
|
yarn.lock
|
||||||
|
|
||||||
# OS
|
# OS
|
||||||
.DS_Store
|
.DS_Store
|
||||||
@ -37,4 +38,6 @@ lerna-debug.log*
|
|||||||
!.vscode/launch.json
|
!.vscode/launch.json
|
||||||
!.vscode/extensions.json
|
!.vscode/extensions.json
|
||||||
|
|
||||||
tmp
|
tmp
|
||||||
|
exportfile
|
||||||
|
userUpload
|
@ -22,15 +22,17 @@
|
|||||||
"@nestjs/common": "^10.0.0",
|
"@nestjs/common": "^10.0.0",
|
||||||
"@nestjs/config": "^3.1.1",
|
"@nestjs/config": "^3.1.1",
|
||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
|
"@nestjs/microservices": "^10.4.4",
|
||||||
"@nestjs/platform-express": "^10.0.0",
|
"@nestjs/platform-express": "^10.0.0",
|
||||||
"@nestjs/serve-static": "^4.0.0",
|
"@nestjs/serve-static": "^4.0.0",
|
||||||
"@nestjs/swagger": "^7.3.0",
|
"@nestjs/swagger": "^7.3.0",
|
||||||
"@nestjs/typeorm": "^10.0.1",
|
"@nestjs/typeorm": "^10.0.1",
|
||||||
"ali-oss": "^6.20.0",
|
"ali-oss": "^6.20.0",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "1.0.0-rc.12",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
"dotenv": "^16.3.2",
|
"dotenv": "^16.3.2",
|
||||||
"fs-extra": "^11.2.0",
|
"fs-extra": "^11.2.0",
|
||||||
|
"ioredis": "^5.4.1",
|
||||||
"joi": "^17.11.0",
|
"joi": "^17.11.0",
|
||||||
"jsonwebtoken": "^9.0.2",
|
"jsonwebtoken": "^9.0.2",
|
||||||
"lodash": "^4.17.21",
|
"lodash": "^4.17.21",
|
||||||
@ -41,7 +43,9 @@
|
|||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
"node-fetch": "^2.7.0",
|
"node-fetch": "^2.7.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
|
"node-xlsx": "^0.24.0",
|
||||||
"qiniu": "^7.11.1",
|
"qiniu": "^7.11.1",
|
||||||
|
"redlock": "^5.0.0-beta.2",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
"svg-captcha": "^1.4.0",
|
"svg-captcha": "^1.4.0",
|
||||||
@ -70,8 +74,9 @@
|
|||||||
"jest": "^29.5.0",
|
"jest": "^29.5.0",
|
||||||
"mongodb-memory-server": "^9.1.4",
|
"mongodb-memory-server": "^9.1.4",
|
||||||
"prettier": "^3.0.0",
|
"prettier": "^3.0.0",
|
||||||
|
"redis-memory-server": "^0.11.0",
|
||||||
"source-map-support": "^0.5.21",
|
"source-map-support": "^0.5.21",
|
||||||
"supertest": "^6.3.3",
|
"supertest": "^7.0.0",
|
||||||
"ts-jest": "^29.1.0",
|
"ts-jest": "^29.1.0",
|
||||||
"ts-loader": "^9.4.3",
|
"ts-loader": "^9.4.3",
|
||||||
"ts-node": "^10.9.1",
|
"ts-node": "^10.9.1",
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { MongoMemoryServer } from 'mongodb-memory-server';
|
import { MongoMemoryServer } from 'mongodb-memory-server';
|
||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
// import { RedisMemoryServer } from 'redis-memory-server';
|
||||||
|
|
||||||
async function startServerAndRunScript() {
|
async function startServerAndRunScript() {
|
||||||
// 启动 MongoDB 内存服务器
|
// 启动 MongoDB 内存服务器
|
||||||
@ -8,12 +9,19 @@ async function startServerAndRunScript() {
|
|||||||
|
|
||||||
console.log('MongoDB Memory Server started:', mongoUri);
|
console.log('MongoDB Memory Server started:', mongoUri);
|
||||||
|
|
||||||
|
// const redisServer = new RedisMemoryServer();
|
||||||
|
// const redisHost = await redisServer.getHost();
|
||||||
|
// const redisPort = await redisServer.getPort();
|
||||||
|
|
||||||
// 通过 spawn 运行另一个脚本,并传递 MongoDB 连接 URL 作为环境变量
|
// 通过 spawn 运行另一个脚本,并传递 MongoDB 连接 URL 作为环境变量
|
||||||
const tsnode = spawn(
|
const tsnode = spawn(
|
||||||
'cross-env',
|
'cross-env',
|
||||||
[
|
[
|
||||||
`XIAOJU_SURVEY_MONGO_URL=${mongoUri}`,
|
`XIAOJU_SURVEY_MONGO_URL=${mongoUri}`,
|
||||||
|
// `XIAOJU_SURVEY_REDIS_HOST=${redisHost}`,
|
||||||
|
// `XIAOJU_SURVEY_REDIS_PORT=${redisPort}`,
|
||||||
'NODE_ENV=development',
|
'NODE_ENV=development',
|
||||||
|
'SERVER_ENV=local',
|
||||||
'npm',
|
'npm',
|
||||||
'run',
|
'run',
|
||||||
'start:dev',
|
'start:dev',
|
||||||
@ -31,9 +39,10 @@ async function startServerAndRunScript() {
|
|||||||
console.error(data);
|
console.error(data);
|
||||||
});
|
});
|
||||||
|
|
||||||
tsnode.on('close', (code) => {
|
tsnode.on('close', async (code) => {
|
||||||
console.log(`Nodemon process exited with code ${code}`);
|
console.log(`Nodemon process exited with code ${code}`);
|
||||||
mongod.stop(); // 停止 MongoDB 内存服务器
|
await mongod.stop(); // 停止 MongoDB 内存服务器
|
||||||
|
// await redisServer.stop();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
64
server/scripts/run-report.ts
Normal file
64
server/scripts/run-report.ts
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
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<PackageJson | null> => {
|
||||||
|
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<void> => {
|
||||||
|
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((e) => {
|
||||||
|
console.log(99999, e);
|
||||||
|
});
|
||||||
|
})();
|
@ -36,16 +36,21 @@ import { MessagePushingLog } from './models/messagePushingLog.entity';
|
|||||||
import { WorkspaceMember } from './models/workspaceMember.entity';
|
import { WorkspaceMember } from './models/workspaceMember.entity';
|
||||||
import { Workspace } from './models/workspace.entity';
|
import { Workspace } from './models/workspace.entity';
|
||||||
import { Collaborator } from './models/collaborator.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 { LoggerProvider } from './logger/logger.provider';
|
||||||
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
||||||
import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
|
import { LogRequestMiddleware } from './middlewares/logRequest.middleware';
|
||||||
import { XiaojuSurveyPluginManager } from './securityPlugin/pluginManager';
|
import { PluginManager } from './securityPlugin/pluginManager';
|
||||||
import { Logger } from './logger';
|
import { Logger } from './logger';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
ConfigModule.forRoot({}),
|
ConfigModule.forRoot({
|
||||||
|
envFilePath: `.env.${process.env.NODE_ENV}`, // 根据 NODE_ENV 动态加载对应的 .env 文件
|
||||||
|
isGlobal: true, // 使配置模块在应用的任何地方可用
|
||||||
|
}),
|
||||||
TypeOrmModule.forRootAsync({
|
TypeOrmModule.forRootAsync({
|
||||||
imports: [ConfigModule],
|
imports: [ConfigModule],
|
||||||
inject: [ConfigService],
|
inject: [ConfigService],
|
||||||
@ -82,6 +87,8 @@ import { Logger } from './logger';
|
|||||||
Workspace,
|
Workspace,
|
||||||
WorkspaceMember,
|
WorkspaceMember,
|
||||||
Collaborator,
|
Collaborator,
|
||||||
|
DownloadTask,
|
||||||
|
Session,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -116,7 +123,7 @@ import { Logger } from './logger';
|
|||||||
export class AppModule {
|
export class AppModule {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly configService: ConfigService,
|
private readonly configService: ConfigService,
|
||||||
private readonly pluginManager: XiaojuSurveyPluginManager,
|
private readonly pluginManager: PluginManager,
|
||||||
) {}
|
) {}
|
||||||
configure(consumer: MiddlewareConsumer) {
|
configure(consumer: MiddlewareConsumer) {
|
||||||
consumer.apply(LogRequestMiddleware).forRoutes('*');
|
consumer.apply(LogRequestMiddleware).forRoutes('*');
|
||||||
|
6
server/src/enums/downloadTaskStatus.ts
Normal file
6
server/src/enums/downloadTaskStatus.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export enum DOWNLOAD_TASK_STATUS {
|
||||||
|
WAITING = 'waiting', // 排队中
|
||||||
|
COMPUTING = 'computing', // 计算中
|
||||||
|
SUCCEED = 'succeed', // 导出成功
|
||||||
|
FAILED = 'failed', // 导出失败
|
||||||
|
}
|
@ -12,6 +12,7 @@ export enum EXCEPTION_CODE {
|
|||||||
SURVEY_TYPE_ERROR = 3003, // 问卷类型错误
|
SURVEY_TYPE_ERROR = 3003, // 问卷类型错误
|
||||||
SURVEY_NOT_FOUND = 3004, // 问卷不存在
|
SURVEY_NOT_FOUND = 3004, // 问卷不存在
|
||||||
SURVEY_CONTENT_NOT_ALLOW = 3005, // 存在禁用内容
|
SURVEY_CONTENT_NOT_ALLOW = 3005, // 存在禁用内容
|
||||||
|
SURVEY_SAVE_CONFLICT = 3006, // 问卷冲突
|
||||||
CAPTCHA_INCORRECT = 4001, // 验证码不正确
|
CAPTCHA_INCORRECT = 4001, // 验证码不正确
|
||||||
WHITELIST_ERROR = 4002, // 白名单校验错误
|
WHITELIST_ERROR = 4002, // 白名单校验错误
|
||||||
|
|
||||||
|
@ -2,15 +2,14 @@
|
|||||||
export enum RECORD_STATUS {
|
export enum RECORD_STATUS {
|
||||||
NEW = 'new', // 新建 | 未发布
|
NEW = 'new', // 新建 | 未发布
|
||||||
PUBLISHED = 'published', // 发布
|
PUBLISHED = 'published', // 发布
|
||||||
CLOSE = 'close', // 关闭
|
EDITING = 'editing', // 编辑
|
||||||
|
FINISHED = 'finished', // 已结束
|
||||||
|
REMOVED = 'removed',
|
||||||
}
|
}
|
||||||
|
|
||||||
export const enum RECORD_SUB_STATUS {
|
export const enum RECORD_SUB_STATUS {
|
||||||
DEFAULT = '', // 默认
|
DEFAULT = '', // 默认
|
||||||
EDITING = 'editing', // 编辑
|
|
||||||
PAUSING = 'pausing', // 暂停
|
PAUSING = 'pausing', // 暂停
|
||||||
REMOVED = 'removed', // 删除
|
|
||||||
FORCE_REMOVED = 'forceRemoved', // 从回收站删除
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 历史类型
|
// 历史类型
|
||||||
|
4
server/src/enums/surveySessionStatus.ts
Normal file
4
server/src/enums/surveySessionStatus.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export enum SESSION_STATUS {
|
||||||
|
ACTIVATED = 'activated',
|
||||||
|
DEACTIVATED = 'deactivated',
|
||||||
|
}
|
68
server/src/guards/__test/session.guard.spec.ts
Normal file
68
server/src/guards/__test/session.guard.spec.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { SessionService } from 'src/modules/survey/services/session.service';
|
||||||
|
import { SessionGuard } from '../session.guard';
|
||||||
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
|
||||||
|
describe('SessionGuard', () => {
|
||||||
|
let sessionGuard: SessionGuard;
|
||||||
|
let reflector: Reflector;
|
||||||
|
let sessionService: SessionService;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
reflector = new Reflector();
|
||||||
|
sessionService = {
|
||||||
|
findOne: jest.fn(),
|
||||||
|
} as unknown as SessionService;
|
||||||
|
sessionGuard = new SessionGuard(reflector, sessionService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return true when sessionId exists and sessionService returns sessionInfo', async () => {
|
||||||
|
const mockSessionId = '12345';
|
||||||
|
const mockSessionInfo = { id: mockSessionId, name: 'test session' };
|
||||||
|
|
||||||
|
const context = {
|
||||||
|
switchToHttp: jest.fn().mockReturnThis(),
|
||||||
|
getRequest: jest.fn().mockReturnValue({
|
||||||
|
sessionId: mockSessionId,
|
||||||
|
}),
|
||||||
|
getHandler: jest.fn(),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue('sessionId');
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(sessionService, 'findOne')
|
||||||
|
.mockResolvedValue(mockSessionInfo as any);
|
||||||
|
|
||||||
|
const result = await sessionGuard.canActivate(context);
|
||||||
|
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
|
||||||
|
expect(result).toBe(true);
|
||||||
|
expect(reflector.get).toHaveBeenCalledWith(
|
||||||
|
'sessionId',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
expect(sessionService.findOne).toHaveBeenCalledWith(mockSessionId);
|
||||||
|
expect(request.sessionInfo).toEqual(mockSessionInfo);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoPermissionException when sessionId is missing', async () => {
|
||||||
|
const context = {
|
||||||
|
switchToHttp: jest.fn().mockReturnThis(),
|
||||||
|
getRequest: jest.fn().mockReturnValue({}),
|
||||||
|
getHandler: jest.fn(),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue('sessionId');
|
||||||
|
|
||||||
|
await expect(sessionGuard.canActivate(context)).rejects.toThrow(
|
||||||
|
NoPermissionException,
|
||||||
|
);
|
||||||
|
expect(reflector.get).toHaveBeenCalledWith(
|
||||||
|
'sessionId',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
30
server/src/guards/session.guard.ts
Normal file
30
server/src/guards/session.guard.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
import { SessionService } from 'src/modules/survey/services/session.service';
|
||||||
|
@Injectable()
|
||||||
|
export class SessionGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private reflector: Reflector,
|
||||||
|
private readonly sessionService: SessionService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const sessionIdKey = this.reflector.get<string>(
|
||||||
|
'sessionId',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const sessionId = get(request, sessionIdKey);
|
||||||
|
|
||||||
|
if (!sessionId) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
const sessionInfo = await this.sessionService.findOne(sessionId);
|
||||||
|
request.sessionInfo = sessionInfo;
|
||||||
|
request.surveyId = sessionInfo.surveyId;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -3,7 +3,6 @@ import { Reflector } from '@nestjs/core';
|
|||||||
import { get } from 'lodash';
|
import { get } from 'lodash';
|
||||||
|
|
||||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
|
||||||
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
|
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
|
||||||
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
|
@ -112,7 +112,7 @@ export enum MemberType {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export interface BaseConf {
|
export interface BaseConf {
|
||||||
begTime: string;
|
beginTime: string;
|
||||||
endTime: string;
|
endTime: string;
|
||||||
answerBegTime: string;
|
answerBegTime: string;
|
||||||
answerEndTime: string;
|
answerEndTime: string;
|
||||||
@ -135,6 +135,17 @@ export interface BaseConf {
|
|||||||
export interface SkinConf {
|
export interface SkinConf {
|
||||||
skinColor: string;
|
skinColor: string;
|
||||||
inputBgColor: string;
|
inputBgColor: string;
|
||||||
|
backgroundConf: {
|
||||||
|
color: string;
|
||||||
|
type: string;
|
||||||
|
image: string;
|
||||||
|
};
|
||||||
|
contentConf: {
|
||||||
|
opacity: number;
|
||||||
|
};
|
||||||
|
themeConf: {
|
||||||
|
color: string;
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface BottomConf {
|
export interface BottomConf {
|
||||||
|
@ -1,15 +1,18 @@
|
|||||||
import * as log4js from 'log4js';
|
import * as log4js from 'log4js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { Request } from 'express';
|
import { Injectable, Scope, Inject } from '@nestjs/common';
|
||||||
|
import { CONTEXT, RequestContext } from '@nestjs/microservices';
|
||||||
|
|
||||||
const log4jsLogger = log4js.getLogger();
|
const log4jsLogger = log4js.getLogger();
|
||||||
|
|
||||||
|
@Injectable({ scope: Scope.REQUEST })
|
||||||
export class Logger {
|
export class Logger {
|
||||||
private static inited = false;
|
private static inited = false;
|
||||||
|
|
||||||
constructor() {}
|
constructor(@Inject(CONTEXT) private readonly ctx: RequestContext) {}
|
||||||
|
|
||||||
static init(config: { filename: string }) {
|
static init(config: { filename: string }) {
|
||||||
if (this.inited) {
|
if (Logger.inited) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
log4js.configure({
|
log4js.configure({
|
||||||
@ -30,25 +33,26 @@ export class Logger {
|
|||||||
default: { appenders: ['app'], level: 'trace' },
|
default: { appenders: ['app'], level: 'trace' },
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Logger.inited = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
_log(message, options: { dltag?: string; level: string; req?: Request }) {
|
_log(message, options: { dltag?: string; level: string }) {
|
||||||
const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
|
const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||||
const level = options?.level;
|
const level = options?.level;
|
||||||
const dltag = options?.dltag ? `${options.dltag}||` : '';
|
const dltag = options?.dltag ? `${options.dltag}||` : '';
|
||||||
const traceIdStr = options?.req?.['traceId']
|
const traceIdStr = this.ctx?.['traceId']
|
||||||
? `traceid=${options?.req?.['traceId']}||`
|
? `traceid=${this.ctx?.['traceId']}||`
|
||||||
: '';
|
: '';
|
||||||
return log4jsLogger[level](
|
return log4jsLogger[level](
|
||||||
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info(message, options?: { dltag?: string; req?: Request }) {
|
info(message, options?: { dltag?: string }) {
|
||||||
return this._log(message, { ...options, level: 'info' });
|
return this._log(message, { ...options, level: 'info' });
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message, options: { dltag?: string; req?: Request }) {
|
error(message, options?: { dltag?: string }) {
|
||||||
return this._log(message, { ...options, level: 'error' });
|
return this._log(message, { ...options, level: 'error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,9 +10,9 @@ const getCountStr = () => {
|
|||||||
|
|
||||||
export const genTraceId = ({ ip }) => {
|
export const genTraceId = ({ ip }) => {
|
||||||
// ip转16位 + 当前时间戳(毫秒级)+自增序列(1000开始自增到9000)+ 当前进程id的后5位
|
// ip转16位 + 当前时间戳(毫秒级)+自增序列(1000开始自增到9000)+ 当前进程id的后5位
|
||||||
ip = ip.replace('::ffff:', '');
|
ip = ip.replace('::ffff:', '').replace('::1', '');
|
||||||
let ipArr;
|
let ipArr;
|
||||||
if (ip.indexOf(':') > 0) {
|
if (ip.indexOf(':') >= 0) {
|
||||||
ipArr = ip.split(':').map((segment) => {
|
ipArr = ip.split(':').map((segment) => {
|
||||||
// 将IPv6每个段转为16位,并补0到长度为4
|
// 将IPv6每个段转为16位,并补0到长度为4
|
||||||
return parseInt(segment, 16).toString(16).padStart(4, '0');
|
return parseInt(segment, 16).toString(16).padStart(4, '0');
|
||||||
@ -20,7 +20,9 @@ export const genTraceId = ({ ip }) => {
|
|||||||
} else {
|
} else {
|
||||||
ipArr = ip
|
ipArr = ip
|
||||||
.split('.')
|
.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)}`;
|
return `${ipArr.join('')}${Date.now().toString()}${getCountStr()}${process.pid.toString().slice(-5)}`;
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { NestFactory } from '@nestjs/core';
|
import { NestFactory } from '@nestjs/core';
|
||||||
import { AppModule } from './app.module';
|
import { AppModule } from './app.module';
|
||||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||||
|
import 'scripts/run-report';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
|
@ -1,4 +1,3 @@
|
|||||||
// logger.middleware.ts
|
|
||||||
import { Injectable, NestMiddleware } from '@nestjs/common';
|
import { Injectable, NestMiddleware } from '@nestjs/common';
|
||||||
import { Request, Response, NextFunction } from 'express';
|
import { Request, Response, NextFunction } from 'express';
|
||||||
import { Logger } from '../logger/index'; // 替换为你实际的logger路径
|
import { Logger } from '../logger/index'; // 替换为你实际的logger路径
|
||||||
@ -20,7 +19,6 @@ export class LogRequestMiddleware implements NestMiddleware {
|
|||||||
`method=${method}||uri=${originalUrl}||ip=${ip}||ua=${userAgent}||query=${query}||body=${body}`,
|
`method=${method}||uri=${originalUrl}||ip=${ip}||ua=${userAgent}||query=${query}||body=${body}`,
|
||||||
{
|
{
|
||||||
dltag: 'request_in',
|
dltag: 'request_in',
|
||||||
req,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -30,7 +28,6 @@ export class LogRequestMiddleware implements NestMiddleware {
|
|||||||
`status=${res.statusCode.toString()}||duration=${duration}ms`,
|
`status=${res.statusCode.toString()}||duration=${duration}ms`,
|
||||||
{
|
{
|
||||||
dltag: 'request_out',
|
dltag: 'request_out',
|
||||||
req,
|
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,53 +1,13 @@
|
|||||||
import { Column, ObjectIdColumn, BeforeInsert, BeforeUpdate } from 'typeorm';
|
import { ObjectIdColumn, CreateDateColumn, UpdateDateColumn } from 'typeorm';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums';
|
|
||||||
|
|
||||||
export class BaseEntity {
|
export class BaseEntity {
|
||||||
@ObjectIdColumn()
|
@ObjectIdColumn()
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
|
|
||||||
@Column()
|
@CreateDateColumn({ type: 'timestamp', precision: 3 })
|
||||||
curStatus: {
|
createdAt: Date;
|
||||||
status: RECORD_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Column()
|
@UpdateDateColumn({ type: 'timestamp', precision: 3 })
|
||||||
subStatus: {
|
updatedAt: Date;
|
||||||
status: RECORD_SUB_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
|
|
||||||
@Column()
|
|
||||||
statusList: Array<{
|
|
||||||
status: RECORD_STATUS | RECORD_SUB_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];
|
|
||||||
}
|
|
||||||
if (!this.subStatus) {
|
|
||||||
const subStatus = { status: RECORD_SUB_STATUS.DEFAULT, date: now };
|
|
||||||
this.subStatus = subStatus;
|
|
||||||
}
|
|
||||||
this.createDate = now;
|
|
||||||
this.updateDate = now;
|
|
||||||
}
|
|
||||||
|
|
||||||
@BeforeUpdate()
|
|
||||||
onUpdate() {
|
|
||||||
this.updateDate = Date.now();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -5,8 +5,7 @@ import { BaseEntity } from './base.entity';
|
|||||||
@Entity({ name: 'captcha' })
|
@Entity({ name: 'captcha' })
|
||||||
export class Captcha extends BaseEntity {
|
export class Captcha extends BaseEntity {
|
||||||
@Index({
|
@Index({
|
||||||
expireAfterSeconds:
|
expireAfterSeconds: 3600,
|
||||||
new Date(Date.now() + 2 * 60 * 60 * 1000).getTime() / 1000,
|
|
||||||
})
|
})
|
||||||
@ObjectIdColumn()
|
@ObjectIdColumn()
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
|
@ -6,8 +6,7 @@ import { BaseEntity } from './base.entity';
|
|||||||
@Entity({ name: 'clientEncrypt' })
|
@Entity({ name: 'clientEncrypt' })
|
||||||
export class ClientEncrypt extends BaseEntity {
|
export class ClientEncrypt extends BaseEntity {
|
||||||
@Index({
|
@Index({
|
||||||
expireAfterSeconds:
|
expireAfterSeconds: 3600,
|
||||||
new Date(Date.now() + 2 * 60 * 60 * 1000).getTime() / 1000,
|
|
||||||
})
|
})
|
||||||
@ObjectIdColumn()
|
@ObjectIdColumn()
|
||||||
_id: ObjectId;
|
_id: ObjectId;
|
||||||
|
@ -11,4 +11,16 @@ export class Collaborator extends BaseEntity {
|
|||||||
|
|
||||||
@Column('jsonb')
|
@Column('jsonb')
|
||||||
permissions: Array<string>;
|
permissions: Array<string>;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creator: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
operator: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
operatorId: string;
|
||||||
}
|
}
|
||||||
|
48
server/src/models/downloadTask.entity.ts
Normal file
48
server/src/models/downloadTask.entity.ts
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
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 {
|
||||||
|
@Column()
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
surveyPath: string;
|
||||||
|
|
||||||
|
// 文件路径
|
||||||
|
@Column()
|
||||||
|
url: string;
|
||||||
|
|
||||||
|
// 文件key
|
||||||
|
@Column()
|
||||||
|
fileKey: string;
|
||||||
|
|
||||||
|
// 任务创建人
|
||||||
|
@Column()
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
|
// 任务创建人
|
||||||
|
@Column()
|
||||||
|
creator: string;
|
||||||
|
|
||||||
|
// 文件名
|
||||||
|
@Column()
|
||||||
|
filename: string;
|
||||||
|
|
||||||
|
// 文件大小
|
||||||
|
@Column()
|
||||||
|
fileSize: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
params: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
isDeleted: boolean;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
deletedAt: Date;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
status: DOWNLOAD_TASK_STATUS;
|
||||||
|
}
|
@ -27,4 +27,10 @@ export class MessagePushingTask extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
isDeleted: boolean;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
deletedAt: Date;
|
||||||
}
|
}
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
import { Entity, Column } from 'typeorm';
|
import { Entity, Column } from 'typeorm';
|
||||||
import { SurveySchemaInterface } from '../interfaces/survey';
|
import { SurveySchemaInterface } from '../interfaces/survey';
|
||||||
import { BaseEntity } from './base.entity';
|
import { BaseEntity } from './base.entity';
|
||||||
|
import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums';
|
||||||
|
|
||||||
@Entity({ name: 'surveyPublish' })
|
@Entity({ name: 'surveyPublish' })
|
||||||
export class ResponseSchema extends BaseEntity {
|
export class ResponseSchema extends BaseEntity {
|
||||||
@ -15,4 +16,19 @@ export class ResponseSchema extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
pageId: string;
|
pageId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
subStatus: {
|
||||||
|
status: RECORD_SUB_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
isDeleted: boolean;
|
||||||
}
|
}
|
||||||
|
22
server/src/models/session.entity.ts
Normal file
22
server/src/models/session.entity.ts
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
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 {
|
||||||
|
@Index({
|
||||||
|
expireAfterSeconds: 3600,
|
||||||
|
})
|
||||||
|
@ObjectIdColumn()
|
||||||
|
_id: ObjectId;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
status: SESSION_STATUS;
|
||||||
|
}
|
@ -19,4 +19,7 @@ export class SurveyHistory extends BaseEntity {
|
|||||||
username: string;
|
username: string;
|
||||||
_id: string;
|
_id: string;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@Column('string')
|
||||||
|
sessionId: string;
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import { Entity, Column } from 'typeorm';
|
import { Entity, Column, BeforeInsert } from 'typeorm';
|
||||||
import { BaseEntity } from './base.entity';
|
import { BaseEntity } from './base.entity';
|
||||||
|
import { RECORD_STATUS, RECORD_SUB_STATUS } from '../enums';
|
||||||
|
|
||||||
@Entity({ name: 'surveyMeta' })
|
@Entity({ name: 'surveyMeta' })
|
||||||
export class SurveyMeta extends BaseEntity {
|
export class SurveyMeta extends BaseEntity {
|
||||||
@ -18,6 +19,9 @@ export class SurveyMeta extends BaseEntity {
|
|||||||
@Column()
|
@Column()
|
||||||
creator: string;
|
creator: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@ -32,4 +36,48 @@ export class SurveyMeta extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
workspaceId: string;
|
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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -27,11 +27,11 @@ export class SurveyResponse extends BaseEntity {
|
|||||||
|
|
||||||
@BeforeInsert()
|
@BeforeInsert()
|
||||||
async onDataInsert() {
|
async onDataInsert() {
|
||||||
return await pluginManager.triggerHook('beforeResponseDataCreate', this);
|
return await pluginManager.triggerHook('encryptResponseData', this);
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterLoad()
|
@AfterLoad()
|
||||||
async onDataLoaded() {
|
async onDataLoaded() {
|
||||||
return await pluginManager.triggerHook('afterResponseDataReaded', this);
|
return await pluginManager.triggerHook('decryptResponseData', this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -3,12 +3,33 @@ import { BaseEntity } from './base.entity';
|
|||||||
|
|
||||||
@Entity({ name: 'workspace' })
|
@Entity({ name: 'workspace' })
|
||||||
export class Workspace extends BaseEntity {
|
export class Workspace extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creator: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
owner: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
name: string;
|
name: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
description: string;
|
description: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
operator: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
operatorId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
isDeleted: boolean;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
deletedAt: Date;
|
||||||
}
|
}
|
||||||
|
@ -11,4 +11,16 @@ export class WorkspaceMember extends BaseEntity {
|
|||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
role: string;
|
role: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creator: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
operator: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
operatorId: string;
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import { UserService } from '../services/user.service';
|
|||||||
import { User } from 'src/models/user.entity';
|
import { User } from 'src/models/user.entity';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { hash256 } from 'src/utils/hash256';
|
import { hash256 } from 'src/utils/hash256';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe('UserService', () => {
|
||||||
@ -145,7 +144,6 @@ describe('UserService', () => {
|
|||||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
expect(userRepository.findOne).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toEqual(userInfo);
|
expect(user).toEqual(userInfo);
|
||||||
@ -163,7 +161,6 @@ describe('UserService', () => {
|
|||||||
expect(findOneSpy).toHaveBeenCalledWith({
|
expect(findOneSpy).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toBe(null);
|
expect(user).toBe(null);
|
||||||
@ -184,7 +181,6 @@ describe('UserService', () => {
|
|||||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
expect(userRepository.findOne).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toEqual(userInfo);
|
expect(user).toEqual(userInfo);
|
||||||
@ -202,7 +198,6 @@ describe('UserService', () => {
|
|||||||
expect(findOneSpy).toHaveBeenCalledWith({
|
expect(findOneSpy).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(user).toBe(null);
|
expect(user).toBe(null);
|
||||||
@ -228,7 +223,6 @@ describe('UserService', () => {
|
|||||||
expect(userRepository.find).toHaveBeenCalledWith({
|
expect(userRepository.find).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
username: new RegExp(username),
|
username: new RegExp(username),
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
skip: 0,
|
skip: 0,
|
||||||
take: 10,
|
take: 10,
|
||||||
@ -263,7 +257,6 @@ describe('UserService', () => {
|
|||||||
_id: {
|
_id: {
|
||||||
$in: idList.map((id) => new ObjectId(id)),
|
$in: idList.map((id) => new ObjectId(id)),
|
||||||
},
|
},
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
select: ['_id', 'username', 'createDate'],
|
select: ['_id', 'username', 'createDate'],
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,12 @@
|
|||||||
import { Controller, Post, Body, HttpCode, Get, Query } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
HttpCode,
|
||||||
|
Get,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
} from '@nestjs/common';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { UserService } from '../services/user.service';
|
import { UserService } from '../services/user.service';
|
||||||
import { CaptchaService } from '../services/captcha.service';
|
import { CaptchaService } from '../services/captcha.service';
|
||||||
@ -187,7 +195,7 @@ export class AuthController {
|
|||||||
/**
|
/**
|
||||||
* 密码强度
|
* 密码强度
|
||||||
*/
|
*/
|
||||||
@Get('register/password/strength')
|
@Get('/password/strength')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async getPasswordStrength(@Query('password') password: string) {
|
async getPasswordStrength(@Query('password') password: string) {
|
||||||
const numberReg = /[0-9]/.test(password);
|
const numberReg = /[0-9]/.test(password);
|
||||||
@ -214,4 +222,28 @@ export class AuthController {
|
|||||||
data: 'Weak',
|
data: 'Weak',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Get('/verifyToken')
|
||||||
|
@HttpCode(200)
|
||||||
|
async verifyToken(@Request() req) {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
if (!token) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
await this.authService.verifyToken(token);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: true,
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: false,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,4 +1,11 @@
|
|||||||
import { Controller, Get, Query, HttpCode, UseGuards } from '@nestjs/common';
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Query,
|
||||||
|
HttpCode,
|
||||||
|
UseGuards,
|
||||||
|
Request,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
@ -43,4 +50,16 @@ export class UserController {
|
|||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
@Get('/getUserInfo')
|
||||||
|
async getUserInfo(@Request() req) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
username: req.user.username,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ import { User } from 'src/models/user.entity';
|
|||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { hash256 } from 'src/utils/hash256';
|
import { hash256 } from 'src/utils/hash256';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
@ -53,9 +52,6 @@ export class UserService {
|
|||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -66,9 +62,6 @@ export class UserService {
|
|||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -79,13 +72,10 @@ export class UserService {
|
|||||||
const list = await this.userRepository.find({
|
const list = await this.userRepository.find({
|
||||||
where: {
|
where: {
|
||||||
username: new RegExp(username),
|
username: new RegExp(username),
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
skip,
|
skip,
|
||||||
take,
|
take,
|
||||||
select: ['_id', 'username', 'createDate'],
|
select: ['_id', 'username', 'createdAt'],
|
||||||
});
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
@ -96,11 +86,8 @@ export class UserService {
|
|||||||
_id: {
|
_id: {
|
||||||
$in: idList.map((item) => new ObjectId(item)),
|
$in: idList.map((item) => new ObjectId(item)),
|
||||||
},
|
},
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
select: ['_id', 'username', 'createDate'],
|
select: ['_id', 'username', 'createdAt'],
|
||||||
});
|
});
|
||||||
return list;
|
return list;
|
||||||
}
|
}
|
||||||
|
@ -14,13 +14,18 @@ export class FileService {
|
|||||||
configKey,
|
configKey,
|
||||||
file,
|
file,
|
||||||
pathPrefix,
|
pathPrefix,
|
||||||
|
filename,
|
||||||
}: {
|
}: {
|
||||||
configKey: string;
|
configKey: string;
|
||||||
file: Express.Multer.File;
|
file: Express.Multer.File;
|
||||||
pathPrefix: string;
|
pathPrefix: string;
|
||||||
|
filename?: string;
|
||||||
}) {
|
}) {
|
||||||
const handler = this.getHandler(configKey);
|
const handler = this.getHandler(configKey);
|
||||||
const { key } = await handler.upload(file, { pathPrefix });
|
const { key } = await handler.upload(file, {
|
||||||
|
pathPrefix,
|
||||||
|
filename,
|
||||||
|
});
|
||||||
const url = await handler.getUrl(key);
|
const url = await handler.getUrl(key);
|
||||||
return {
|
return {
|
||||||
key,
|
key,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { join, dirname } from 'path';
|
import { join, dirname, sep } from 'path';
|
||||||
import fse from 'fs-extra';
|
import fse from 'fs-extra';
|
||||||
import { createWriteStream } from 'fs';
|
import { createWriteStream } from 'fs';
|
||||||
import { FileUploadHandler } from './uploadHandler.interface';
|
import { FileUploadHandler } from './uploadHandler.interface';
|
||||||
@ -12,13 +12,20 @@ export class LocalHandler implements FileUploadHandler {
|
|||||||
|
|
||||||
async upload(
|
async upload(
|
||||||
file: Express.Multer.File,
|
file: Express.Multer.File,
|
||||||
options?: { pathPrefix?: string },
|
options?: { pathPrefix?: string; filename?: string },
|
||||||
): Promise<{ key: string }> {
|
): Promise<{ key: string }> {
|
||||||
const filename = await generateUniqueFilename(file.originalname);
|
let filename;
|
||||||
|
if (options?.filename) {
|
||||||
|
filename = file.filename;
|
||||||
|
} else {
|
||||||
|
filename = await generateUniqueFilename(file.originalname);
|
||||||
|
}
|
||||||
const filePath = join(
|
const filePath = join(
|
||||||
options?.pathPrefix ? options?.pathPrefix : '',
|
options?.pathPrefix ? options?.pathPrefix : '',
|
||||||
filename,
|
filename,
|
||||||
);
|
)
|
||||||
|
.split(sep)
|
||||||
|
.join('/');
|
||||||
const physicalPath = join(this.physicalRootPath, filePath);
|
const physicalPath = join(this.physicalRootPath, filePath);
|
||||||
await fse.mkdir(dirname(physicalPath), { recursive: true });
|
await fse.mkdir(dirname(physicalPath), { recursive: true });
|
||||||
const writeStream = createWriteStream(physicalPath);
|
const writeStream = createWriteStream(physicalPath);
|
||||||
@ -35,6 +42,10 @@ export class LocalHandler implements FileUploadHandler {
|
|||||||
}
|
}
|
||||||
|
|
||||||
getUrl(key: string): string {
|
getUrl(key: string): string {
|
||||||
|
if (process.env.SERVER_ENV === 'local') {
|
||||||
|
const port = process.env.PORT || 3000;
|
||||||
|
return `http://localhost:${port}/${key}`;
|
||||||
|
}
|
||||||
return `/${key}`;
|
return `/${key}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -10,7 +10,6 @@ import { MessagePushingLogService } from '../services/messagePushingLog.service'
|
|||||||
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
||||||
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
@ -121,7 +120,6 @@ describe('MessagePushingTaskService', () => {
|
|||||||
ownerId: mockOwnerId,
|
ownerId: mockOwnerId,
|
||||||
surveys: { $all: [surveyId] },
|
surveys: { $all: [surveyId] },
|
||||||
triggerHook: hook,
|
triggerHook: hook,
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -146,7 +144,6 @@ describe('MessagePushingTaskService', () => {
|
|||||||
where: {
|
where: {
|
||||||
ownerId: mockOwnerId,
|
ownerId: mockOwnerId,
|
||||||
_id: new ObjectId(taskId),
|
_id: new ObjectId(taskId),
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -161,10 +158,6 @@ describe('MessagePushingTaskService', () => {
|
|||||||
pushAddress: 'http://update.example.com',
|
pushAddress: 'http://update.example.com',
|
||||||
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
surveys: ['new survey id'],
|
surveys: ['new survey id'],
|
||||||
subStatus: {
|
|
||||||
status: RECORD_SUB_STATUS.EDITING,
|
|
||||||
date: Date.now(),
|
|
||||||
},
|
|
||||||
};
|
};
|
||||||
const existingTask = new MessagePushingTask();
|
const existingTask = new MessagePushingTask();
|
||||||
existingTask._id = new ObjectId(taskId);
|
existingTask._id = new ObjectId(taskId);
|
||||||
@ -197,34 +190,26 @@ describe('MessagePushingTaskService', () => {
|
|||||||
const taskId = '65afc62904d5db18534c0f78';
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
|
||||||
const updateResult = { modifiedCount: 1 };
|
const updateResult = { modifiedCount: 1 };
|
||||||
const mockOwnerId = '66028642292c50f8b71a9eee';
|
const mockOperatorId = '66028642292c50f8b71a9eee';
|
||||||
|
const mockOperator = 'mockOperator';
|
||||||
|
|
||||||
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
||||||
|
|
||||||
const result = await service.remove({
|
const result = await service.remove({
|
||||||
id: taskId,
|
id: taskId,
|
||||||
ownerId: mockOwnerId,
|
operatorId: mockOperatorId,
|
||||||
|
operator: mockOperator,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual(updateResult);
|
expect(result).toEqual(updateResult);
|
||||||
expect(repository.updateOne).toHaveBeenCalledWith(
|
expect(repository.updateOne).toHaveBeenCalledWith(
|
||||||
{
|
{
|
||||||
ownerId: mockOwnerId,
|
ownerId: mockOperatorId,
|
||||||
_id: new ObjectId(taskId),
|
_id: new ObjectId(taskId),
|
||||||
'subStatus.status': { $ne: RECORD_SUB_STATUS.REMOVED },
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
subStatus: {
|
isDeleted: true,
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
date: expect.any(Number),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
$push: {
|
|
||||||
statusList: {
|
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
date: expect.any(Number),
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -150,8 +150,9 @@ export class MessagePushingTaskController {
|
|||||||
async remove(@Request() req, @Param('id') id: string) {
|
async remove(@Request() req, @Param('id') id: string) {
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const res = await this.messagePushingTaskService.remove({
|
const res = await this.messagePushingTaskService.remove({
|
||||||
ownerId: userId,
|
|
||||||
id,
|
id,
|
||||||
|
operator: req.user.username,
|
||||||
|
operatorId: userId,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -4,7 +4,6 @@ import {
|
|||||||
MESSAGE_PUSHING_TYPE,
|
MESSAGE_PUSHING_TYPE,
|
||||||
MESSAGE_PUSHING_HOOK,
|
MESSAGE_PUSHING_HOOK,
|
||||||
} from 'src/enums/messagePushing';
|
} from 'src/enums/messagePushing';
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
|
||||||
|
|
||||||
export class MessagePushingTaskDto {
|
export class MessagePushingTaskDto {
|
||||||
@ApiProperty({ description: '任务id' })
|
@ApiProperty({ description: '任务id' })
|
||||||
@ -27,12 +26,6 @@ export class MessagePushingTaskDto {
|
|||||||
|
|
||||||
@ApiProperty({ description: '所有者' })
|
@ApiProperty({ description: '所有者' })
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '任务状态', required: false })
|
|
||||||
curStatus?: {
|
|
||||||
status: RECORD_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export class CodeDto {
|
export class CodeDto {
|
||||||
|
@ -1,5 +1,4 @@
|
|||||||
import { ApiProperty } from '@nestjs/swagger';
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
import {
|
import {
|
||||||
MESSAGE_PUSHING_TYPE,
|
MESSAGE_PUSHING_TYPE,
|
||||||
MESSAGE_PUSHING_HOOK,
|
MESSAGE_PUSHING_HOOK,
|
||||||
@ -20,15 +19,4 @@ export class UpdateMessagePushingTaskDto {
|
|||||||
|
|
||||||
@ApiProperty({ description: '绑定的问卷id', required: false })
|
@ApiProperty({ description: '绑定的问卷id', required: false })
|
||||||
surveys?: string[];
|
surveys?: string[];
|
||||||
|
|
||||||
@ApiProperty({ description: '任务状态', required: false })
|
|
||||||
curStatus?: {
|
|
||||||
status: RECORD_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
@ApiProperty({ description: '任务子状态', required: false })
|
|
||||||
subStatus?: {
|
|
||||||
status: RECORD_SUB_STATUS;
|
|
||||||
date: number;
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
@ -6,7 +6,6 @@ import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
|||||||
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
||||||
import { MessagePushingLogService } from './messagePushingLog.service';
|
import { MessagePushingLogService } from './messagePushingLog.service';
|
||||||
import { httpPost } from 'src/utils/request';
|
import { httpPost } from 'src/utils/request';
|
||||||
@ -44,8 +43,8 @@ export class MessagePushingTaskService {
|
|||||||
ownerId?: string;
|
ownerId?: string;
|
||||||
}): Promise<MessagePushingTask[]> {
|
}): Promise<MessagePushingTask[]> {
|
||||||
const where: Record<string, any> = {
|
const where: Record<string, any> = {
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (surveyId) {
|
if (surveyId) {
|
||||||
@ -75,8 +74,8 @@ export class MessagePushingTaskService {
|
|||||||
where: {
|
where: {
|
||||||
ownerId,
|
ownerId,
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
@ -103,25 +102,26 @@ export class MessagePushingTaskService {
|
|||||||
const updatedTask = Object.assign(existingTask, updateData);
|
const updatedTask = Object.assign(existingTask, updateData);
|
||||||
return await this.messagePushingTaskRepository.save(updatedTask);
|
return await this.messagePushingTaskRepository.save(updatedTask);
|
||||||
}
|
}
|
||||||
async remove({ id, ownerId }: { id: string; ownerId: string }) {
|
|
||||||
const subStatus = {
|
async remove({
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
id,
|
||||||
date: Date.now(),
|
operator,
|
||||||
};
|
operatorId,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
operator: string;
|
||||||
|
operatorId: string;
|
||||||
|
}) {
|
||||||
return this.messagePushingTaskRepository.updateOne(
|
return this.messagePushingTaskRepository.updateOne(
|
||||||
{
|
{
|
||||||
ownerId,
|
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
subStatus,
|
isDeleted: true,
|
||||||
},
|
operator,
|
||||||
$push: {
|
operatorId,
|
||||||
statusList: subStatus as never,
|
deletedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -146,6 +146,9 @@ export class MessagePushingTaskService {
|
|||||||
$push: {
|
$push: {
|
||||||
surveys: surveyId as never,
|
surveys: surveyId as never,
|
||||||
},
|
},
|
||||||
|
$set: {
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
9
server/src/modules/redis/redis.module.ts
Normal file
9
server/src/modules/redis/redis.module.ts
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
// src/redis/redis.module.ts
|
||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { RedisService } from './redis.service';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
providers: [RedisService],
|
||||||
|
exports: [RedisService],
|
||||||
|
})
|
||||||
|
export class RedisModule {}
|
32
server/src/modules/redis/redis.service.ts
Normal file
32
server/src/modules/redis/redis.service.ts
Normal file
@ -0,0 +1,32 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Redis } from 'ioredis';
|
||||||
|
import Redlock, { Lock } from 'redlock';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class RedisService {
|
||||||
|
private readonly redisClient: Redis;
|
||||||
|
private readonly redlock: Redlock;
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
this.redisClient = new Redis({
|
||||||
|
host: process.env.XIAOJU_SURVEY_REDIS_HOST,
|
||||||
|
port: parseInt(process.env.XIAOJU_SURVEY_REDIS_PORT),
|
||||||
|
password: process.env.XIAOJU_SURVEY_REDIS_PASSWORD || undefined,
|
||||||
|
username: process.env.XIAOJU_SURVEY_REDIS_USERNAME || undefined,
|
||||||
|
db: parseInt(process.env.XIAOJU_SURVEY_REDIS_DB) || 0,
|
||||||
|
});
|
||||||
|
this.redlock = new Redlock([this.redisClient], {
|
||||||
|
retryCount: 10,
|
||||||
|
retryDelay: 200, // ms
|
||||||
|
retryJitter: 200, // ms
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async lockResource(resource: string, ttl: number): Promise<Lock> {
|
||||||
|
return this.redlock.acquire([resource], ttl);
|
||||||
|
}
|
||||||
|
|
||||||
|
async unlockResource(lock: Lock): Promise<void> {
|
||||||
|
await lock.release();
|
||||||
|
}
|
||||||
|
}
|
@ -191,7 +191,6 @@ describe('CollaboratorController', () => {
|
|||||||
describe('getSurveyCollaboratorList', () => {
|
describe('getSurveyCollaboratorList', () => {
|
||||||
it('should return collaborator list', async () => {
|
it('should return collaborator list', async () => {
|
||||||
const query = { surveyId: 'surveyId' };
|
const query = { surveyId: 'surveyId' };
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
const result = [
|
const result = [
|
||||||
{ _id: 'collaboratorId', userId: 'userId', username: '' },
|
{ _id: 'collaboratorId', userId: 'userId', username: '' },
|
||||||
];
|
];
|
||||||
@ -202,7 +201,7 @@ describe('CollaboratorController', () => {
|
|||||||
|
|
||||||
jest.spyOn(userService, 'getUserListByIds').mockResolvedValueOnce([]);
|
jest.spyOn(userService, 'getUserListByIds').mockResolvedValueOnce([]);
|
||||||
|
|
||||||
const response = await controller.getSurveyCollaboratorList(query, req);
|
const response = await controller.getSurveyCollaboratorList(query);
|
||||||
|
|
||||||
expect(response).toEqual({
|
expect(response).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -214,11 +213,10 @@ describe('CollaboratorController', () => {
|
|||||||
const query: GetSurveyCollaboratorListDto = {
|
const query: GetSurveyCollaboratorListDto = {
|
||||||
surveyId: '',
|
surveyId: '',
|
||||||
};
|
};
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
|
|
||||||
await expect(
|
await expect(controller.getSurveyCollaboratorList(query)).rejects.toThrow(
|
||||||
controller.getSurveyCollaboratorList(query, req),
|
HttpException,
|
||||||
).rejects.toThrow(HttpException);
|
);
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -230,14 +228,13 @@ describe('CollaboratorController', () => {
|
|||||||
userId: 'userId',
|
userId: 'userId',
|
||||||
permissions: ['read'],
|
permissions: ['read'],
|
||||||
};
|
};
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
const result = { _id: 'userId', permissions: ['read'] };
|
const result = { _id: 'userId', permissions: ['read'] };
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(collaboratorService, 'changeUserPermission')
|
.spyOn(collaboratorService, 'changeUserPermission')
|
||||||
.mockResolvedValue(result);
|
.mockResolvedValue(result);
|
||||||
|
|
||||||
const response = await controller.changeUserPermission(reqBody, req);
|
const response = await controller.changeUserPermission(reqBody);
|
||||||
|
|
||||||
expect(response).toEqual({
|
expect(response).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -251,11 +248,10 @@ describe('CollaboratorController', () => {
|
|||||||
userId: '',
|
userId: '',
|
||||||
permissions: ['surveyManage'],
|
permissions: ['surveyManage'],
|
||||||
};
|
};
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
|
|
||||||
await expect(
|
await expect(controller.changeUserPermission(reqBody)).rejects.toThrow(
|
||||||
controller.changeUserPermission(reqBody, req),
|
HttpException,
|
||||||
).rejects.toThrow(HttpException);
|
);
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -263,14 +259,13 @@ describe('CollaboratorController', () => {
|
|||||||
describe('deleteCollaborator', () => {
|
describe('deleteCollaborator', () => {
|
||||||
it('should delete collaborator successfully', async () => {
|
it('should delete collaborator successfully', async () => {
|
||||||
const query = { surveyId: 'surveyId', userId: 'userId' };
|
const query = { surveyId: 'surveyId', userId: 'userId' };
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
const result = { acknowledged: true, deletedCount: 1 };
|
const result = { acknowledged: true, deletedCount: 1 };
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(collaboratorService, 'deleteCollaborator')
|
.spyOn(collaboratorService, 'deleteCollaborator')
|
||||||
.mockResolvedValue(result);
|
.mockResolvedValue(result);
|
||||||
|
|
||||||
const response = await controller.deleteCollaborator(query, req);
|
const response = await controller.deleteCollaborator(query);
|
||||||
|
|
||||||
expect(response).toEqual({
|
expect(response).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -280,9 +275,8 @@ describe('CollaboratorController', () => {
|
|||||||
|
|
||||||
it('should throw an exception if validation fails', async () => {
|
it('should throw an exception if validation fails', async () => {
|
||||||
const query = { surveyId: '', userId: '' };
|
const query = { surveyId: '', userId: '' };
|
||||||
const req = { user: { _id: 'userId' } };
|
|
||||||
|
|
||||||
await expect(controller.deleteCollaborator(query, req)).rejects.toThrow(
|
await expect(controller.deleteCollaborator(query)).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
|
@ -8,7 +8,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
@ -27,7 +27,7 @@ describe('DataStatisticController', () => {
|
|||||||
let controller: DataStatisticController;
|
let controller: DataStatisticController;
|
||||||
let dataStatisticService: DataStatisticService;
|
let dataStatisticService: DataStatisticService;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
let pluginManager: XiaojuSurveyPluginManager;
|
let pluginManager: PluginManager;
|
||||||
let logger: Logger;
|
let logger: Logger;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
@ -70,9 +70,7 @@ describe('DataStatisticController', () => {
|
|||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
pluginManager = module.get<XiaojuSurveyPluginManager>(
|
pluginManager = module.get<PluginManager>(PluginManager);
|
||||||
XiaojuSurveyPluginManager,
|
|
||||||
);
|
|
||||||
logger = module.get<Logger>(Logger);
|
logger = module.get<Logger>(Logger);
|
||||||
|
|
||||||
pluginManager.registerPlugin(
|
pluginManager.registerPlugin(
|
||||||
@ -90,7 +88,7 @@ describe('DataStatisticController', () => {
|
|||||||
const mockRequest = {
|
const mockRequest = {
|
||||||
query: {
|
query: {
|
||||||
surveyId,
|
surveyId,
|
||||||
isDesensitive: false,
|
isMasked: false,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
@ -123,7 +121,7 @@ describe('DataStatisticController', () => {
|
|||||||
.spyOn(dataStatisticService, 'getDataTable')
|
.spyOn(dataStatisticService, 'getDataTable')
|
||||||
.mockResolvedValueOnce(mockDataTable);
|
.mockResolvedValueOnce(mockDataTable);
|
||||||
|
|
||||||
const result = await controller.data(mockRequest.query, mockRequest);
|
const result = await controller.data(mockRequest.query);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -131,12 +129,12 @@ describe('DataStatisticController', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return data table with isDesensitive', async () => {
|
it('should return data table with isMasked', async () => {
|
||||||
const surveyId = new ObjectId().toString();
|
const surveyId = new ObjectId().toString();
|
||||||
const mockRequest = {
|
const mockRequest = {
|
||||||
query: {
|
query: {
|
||||||
surveyId,
|
surveyId,
|
||||||
isDesensitive: true,
|
isMasked: true,
|
||||||
page: 1,
|
page: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
},
|
},
|
||||||
@ -169,7 +167,7 @@ describe('DataStatisticController', () => {
|
|||||||
.spyOn(dataStatisticService, 'getDataTable')
|
.spyOn(dataStatisticService, 'getDataTable')
|
||||||
.mockResolvedValueOnce(mockDataTable);
|
.mockResolvedValueOnce(mockDataTable);
|
||||||
|
|
||||||
const result = await controller.data(mockRequest.query, mockRequest);
|
const result = await controller.data(mockRequest.query);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -187,9 +185,9 @@ describe('DataStatisticController', () => {
|
|||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
await expect(
|
await expect(controller.data(mockRequest.query)).rejects.toThrow(
|
||||||
controller.data(mockRequest.query, mockRequest),
|
HttpException,
|
||||||
).rejects.toThrow(HttpException);
|
);
|
||||||
expect(logger.error).toHaveBeenCalledTimes(1);
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
@ -235,7 +233,7 @@ describe('DataStatisticController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
begTime: '2024-05-31 20:31:36',
|
beginTime: '2024-05-31 20:31:36',
|
||||||
endTime: '2034-05-31 20:31:36',
|
endTime: '2034-05-31 20:31:36',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
showVoteProcess: 'allow',
|
showVoteProcess: 'allow',
|
||||||
@ -251,6 +249,8 @@ describe('DataStatisticController', () => {
|
|||||||
skinConf: {
|
skinConf: {
|
||||||
backgroundConf: {
|
backgroundConf: {
|
||||||
color: '#fff',
|
color: '#fff',
|
||||||
|
type: 'color',
|
||||||
|
image: '',
|
||||||
},
|
},
|
||||||
themeConf: {
|
themeConf: {
|
||||||
color: '#ffa600',
|
color: '#ffa600',
|
||||||
|
@ -11,7 +11,7 @@ import { cloneDeep } from 'lodash';
|
|||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
|
|
||||||
describe('DataStatisticService', () => {
|
describe('DataStatisticService', () => {
|
||||||
@ -34,9 +34,7 @@ describe('DataStatisticService', () => {
|
|||||||
surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>(
|
surveyResponseRepository = module.get<MongoRepository<SurveyResponse>>(
|
||||||
getRepositoryToken(SurveyResponse),
|
getRepositoryToken(SurveyResponse),
|
||||||
);
|
);
|
||||||
const manager = module.get<XiaojuSurveyPluginManager>(
|
const manager = module.get<PluginManager>(PluginManager);
|
||||||
XiaojuSurveyPluginManager,
|
|
||||||
);
|
|
||||||
manager.registerPlugin(
|
manager.registerPlugin(
|
||||||
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
||||||
);
|
);
|
||||||
@ -204,7 +202,7 @@ describe('DataStatisticService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return desensitive table data', async () => {
|
it('should return desensitized table data', async () => {
|
||||||
const mockSchema = cloneDeep(mockSensitiveResponseSchema);
|
const mockSchema = cloneDeep(mockSensitiveResponseSchema);
|
||||||
const surveyResponseList: Array<SurveyResponse> = [
|
const surveyResponseList: Array<SurveyResponse> = [
|
||||||
{
|
{
|
||||||
|
259
server/src/modules/survey/__test/downloadTask.controller.spec.ts
Normal file
259
server/src/modules/survey/__test/downloadTask.controller.spec.ts
Normal file
@ -0,0 +1,259 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { DownloadTaskController } from '../controllers/downloadTask.controller';
|
||||||
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
|
import { DownloadTaskService } from '../services/downloadTask.service';
|
||||||
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
|
||||||
|
describe('DownloadTaskController', () => {
|
||||||
|
let controller: DownloadTaskController;
|
||||||
|
let responseSchemaService: ResponseSchemaService;
|
||||||
|
let downloadTaskService: DownloadTaskService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [DownloadTaskController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: ResponseSchemaService,
|
||||||
|
useValue: {
|
||||||
|
getResponseSchemaByPageId: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: DownloadTaskService,
|
||||||
|
useValue: {
|
||||||
|
createDownloadTask: jest.fn(),
|
||||||
|
processDownloadTask: jest.fn(),
|
||||||
|
getDownloadTaskList: jest.fn(),
|
||||||
|
getDownloadTaskById: jest.fn(),
|
||||||
|
deleteDownloadTask: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: AuthService,
|
||||||
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
|
varifytoken() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: CollaboratorService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SurveyMetaService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Authentication,
|
||||||
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
|
canActivate: () => true,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SurveyGuard,
|
||||||
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
|
canActivate: () => true,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<DownloadTaskController>(DownloadTaskController);
|
||||||
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
|
ResponseSchemaService,
|
||||||
|
);
|
||||||
|
downloadTaskService = module.get<DownloadTaskService>(DownloadTaskService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createTask', () => {
|
||||||
|
it('should create a download task successfully', async () => {
|
||||||
|
const mockReqBody = {
|
||||||
|
surveyId: new ObjectId().toString(),
|
||||||
|
isMasked: false,
|
||||||
|
};
|
||||||
|
const mockReq = { user: { _id: 'mockUserId', username: 'mockUsername' } };
|
||||||
|
const mockTaskId = 'mockTaskId';
|
||||||
|
jest
|
||||||
|
.spyOn(responseSchemaService, 'getResponseSchemaByPageId')
|
||||||
|
.mockResolvedValue({} as any);
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'createDownloadTask')
|
||||||
|
.mockResolvedValue(mockTaskId);
|
||||||
|
|
||||||
|
const result = await controller.createTask(mockReqBody, mockReq);
|
||||||
|
|
||||||
|
expect(
|
||||||
|
responseSchemaService.getResponseSchemaByPageId,
|
||||||
|
).toHaveBeenCalledWith(mockReqBody.surveyId);
|
||||||
|
expect(downloadTaskService.createDownloadTask).toHaveBeenCalledWith({
|
||||||
|
surveyId: mockReqBody.surveyId,
|
||||||
|
responseSchema: {},
|
||||||
|
creatorId: mockReq.user._id.toString(),
|
||||||
|
creator: mockReq.user.username,
|
||||||
|
params: { isMasked: mockReqBody.isMasked },
|
||||||
|
});
|
||||||
|
expect(downloadTaskService.processDownloadTask).toHaveBeenCalledWith({
|
||||||
|
taskId: mockTaskId,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ code: 200, data: { taskId: mockTaskId } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw HttpException if validation fails', async () => {
|
||||||
|
const mockReqBody: any = { isMasked: false };
|
||||||
|
const mockReq = { user: { _id: 'mockUserId', username: 'mockUsername' } };
|
||||||
|
|
||||||
|
await expect(controller.createTask(mockReqBody, mockReq)).rejects.toThrow(
|
||||||
|
HttpException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('downloadList', () => {
|
||||||
|
it('should return the download task list', async () => {
|
||||||
|
const mockQueryInfo = { pageIndex: 1, pageSize: 10 };
|
||||||
|
const mockReq = { user: { _id: 'mockUserId' } };
|
||||||
|
const mockTaskList: any = {
|
||||||
|
total: 1,
|
||||||
|
list: [
|
||||||
|
{
|
||||||
|
_id: 'mockTaskId',
|
||||||
|
curStatus: 'completed',
|
||||||
|
filename: 'mockFile.csv',
|
||||||
|
url: 'http://mock-url.com',
|
||||||
|
fileSize: 1024,
|
||||||
|
createDate: Date.now(),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'getDownloadTaskList')
|
||||||
|
.mockResolvedValue(mockTaskList);
|
||||||
|
|
||||||
|
const result = await controller.downloadList(mockQueryInfo, mockReq);
|
||||||
|
|
||||||
|
expect(downloadTaskService.getDownloadTaskList).toHaveBeenCalledWith({
|
||||||
|
creatorId: mockReq.user._id.toString(),
|
||||||
|
pageIndex: mockQueryInfo.pageIndex,
|
||||||
|
pageSize: mockQueryInfo.pageSize,
|
||||||
|
});
|
||||||
|
expect(result.data.total).toEqual(mockTaskList.total);
|
||||||
|
expect(result.data.list[0].taskId).toEqual(
|
||||||
|
mockTaskList.list[0]._id.toString(),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw HttpException if validation fails', async () => {
|
||||||
|
const mockQueryInfo: any = { pageIndex: 'invalid', pageSize: 10 };
|
||||||
|
const mockReq = { user: { _id: 'mockUserId' } };
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.downloadList(mockQueryInfo, mockReq),
|
||||||
|
).rejects.toThrow(HttpException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDownloadTask', () => {
|
||||||
|
it('should return a download task', async () => {
|
||||||
|
const mockQuery = { taskId: 'mockTaskId' };
|
||||||
|
const mockReq = { user: { _id: 'mockUserId' } };
|
||||||
|
const mockTaskInfo: any = {
|
||||||
|
_id: 'mockTaskId',
|
||||||
|
creatorId: 'mockUserId',
|
||||||
|
curStatus: 'completed',
|
||||||
|
};
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
||||||
|
.mockResolvedValue(mockTaskInfo);
|
||||||
|
|
||||||
|
const result = await controller.getDownloadTask(mockQuery, mockReq);
|
||||||
|
|
||||||
|
expect(downloadTaskService.getDownloadTaskById).toHaveBeenCalledWith({
|
||||||
|
taskId: mockQuery.taskId,
|
||||||
|
});
|
||||||
|
expect(result.data.taskId).toEqual(mockTaskInfo._id.toString());
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoPermissionException if user has no permission', async () => {
|
||||||
|
const mockQuery = { taskId: 'mockTaskId' };
|
||||||
|
const mockReq = { user: { _id: new ObjectId() } };
|
||||||
|
const mockTaskInfo: any = {
|
||||||
|
_id: 'mockTaskId',
|
||||||
|
creatorId: 'mockUserId',
|
||||||
|
curStatus: 'completed',
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
||||||
|
.mockResolvedValue(mockTaskInfo);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.getDownloadTask(mockQuery, mockReq),
|
||||||
|
).rejects.toThrow(new NoPermissionException('没有权限'));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteFileByName', () => {
|
||||||
|
it('should delete a download task successfully', async () => {
|
||||||
|
const mockBody = { taskId: 'mockTaskId' };
|
||||||
|
const mockReq = { user: { _id: 'mockUserId' } };
|
||||||
|
const mockTaskInfo: any = {
|
||||||
|
_id: new ObjectId(),
|
||||||
|
creatorId: 'mockUserId',
|
||||||
|
};
|
||||||
|
const mockDelRes = { modifiedCount: 1 };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
||||||
|
.mockResolvedValue(mockTaskInfo);
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'deleteDownloadTask')
|
||||||
|
.mockResolvedValue(mockDelRes);
|
||||||
|
|
||||||
|
const result = await controller.deleteFileByName(mockBody, mockReq);
|
||||||
|
|
||||||
|
expect(downloadTaskService.deleteDownloadTask).toHaveBeenCalledWith({
|
||||||
|
taskId: mockBody.taskId,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ code: 200, data: true });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw HttpException if task does not exist', async () => {
|
||||||
|
const mockBody = { taskId: 'mockTaskId' };
|
||||||
|
const mockReq = { user: { _id: 'mockUserId' } };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskService, 'getDownloadTaskById')
|
||||||
|
.mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.deleteFileByName(mockBody, mockReq),
|
||||||
|
).rejects.toThrow(
|
||||||
|
new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
191
server/src/modules/survey/__test/downloadTask.service.spec.ts
Normal file
191
server/src/modules/survey/__test/downloadTask.service.spec.ts
Normal file
@ -0,0 +1,191 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { DownloadTaskService } from '../services/downloadTask.service';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { DownloadTask } from 'src/models/downloadTask.entity';
|
||||||
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
|
import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service';
|
||||||
|
import { DataStatisticService } from '../services/dataStatistic.service';
|
||||||
|
import { FileService } from 'src/modules/file/services/file.service';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
describe('DownloadTaskService', () => {
|
||||||
|
let service: DownloadTaskService;
|
||||||
|
let downloadTaskRepository: MongoRepository<DownloadTask>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
DownloadTaskService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(DownloadTask),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(SurveyResponse),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: ResponseSchemaService,
|
||||||
|
useValue: {
|
||||||
|
getResponseSchemaByPageId: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: DataStatisticService,
|
||||||
|
useValue: {
|
||||||
|
getDataTable: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: FileService,
|
||||||
|
useValue: {
|
||||||
|
upload: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<DownloadTaskService>(DownloadTaskService);
|
||||||
|
downloadTaskRepository = module.get<MongoRepository<DownloadTask>>(
|
||||||
|
getRepositoryToken(DownloadTask),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createDownloadTask', () => {
|
||||||
|
it('should create and save a download task', async () => {
|
||||||
|
const mockTaskId = new ObjectId().toString();
|
||||||
|
const mockDownloadTask = { _id: new ObjectId(mockTaskId) };
|
||||||
|
const mockParams: any = {
|
||||||
|
surveyId: 'survey1',
|
||||||
|
responseSchema: { title: 'test-title', surveyPath: '/path' },
|
||||||
|
creatorId: 'creator1',
|
||||||
|
creator: 'creatorName',
|
||||||
|
params: { isMasked: true },
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskRepository, 'create')
|
||||||
|
.mockReturnValue(mockDownloadTask as any);
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskRepository, 'save')
|
||||||
|
.mockResolvedValue(mockDownloadTask as any);
|
||||||
|
|
||||||
|
const result = await service.createDownloadTask(mockParams);
|
||||||
|
|
||||||
|
expect(downloadTaskRepository.create).toHaveBeenCalledWith({
|
||||||
|
surveyId: mockParams.surveyId,
|
||||||
|
surveyPath: mockParams.responseSchema.surveyPath,
|
||||||
|
fileSize: '计算中',
|
||||||
|
creatorId: mockParams.creatorId,
|
||||||
|
creator: mockParams.creator,
|
||||||
|
params: {
|
||||||
|
...mockParams.params,
|
||||||
|
title: mockParams.responseSchema.title,
|
||||||
|
},
|
||||||
|
filename: expect.any(String),
|
||||||
|
});
|
||||||
|
expect(downloadTaskRepository.save).toHaveBeenCalled();
|
||||||
|
expect(result).toEqual(mockTaskId);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDownloadTaskList', () => {
|
||||||
|
it('should return task list and total count', async () => {
|
||||||
|
const mockCreatorId = 'creator1';
|
||||||
|
const mockTasks = [{ _id: '1' }, { _id: '2' }];
|
||||||
|
const mockTotal = 2;
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskRepository, 'findAndCount')
|
||||||
|
.mockResolvedValue([mockTasks as any, mockTotal]);
|
||||||
|
|
||||||
|
const result = await service.getDownloadTaskList({
|
||||||
|
creatorId: mockCreatorId,
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(downloadTaskRepository.findAndCount).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
creatorId: mockCreatorId,
|
||||||
|
},
|
||||||
|
take: 10,
|
||||||
|
skip: 0,
|
||||||
|
order: { createDate: -1 },
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
total: mockTotal,
|
||||||
|
list: mockTasks,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getDownloadTaskById', () => {
|
||||||
|
it('should return task by id', async () => {
|
||||||
|
const mockTaskId = new ObjectId().toString();
|
||||||
|
const mockTask = { _id: new ObjectId(mockTaskId) };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskRepository, 'find')
|
||||||
|
.mockResolvedValue([mockTask as any]);
|
||||||
|
|
||||||
|
const result = await service.getDownloadTaskById({ taskId: mockTaskId });
|
||||||
|
|
||||||
|
expect(downloadTaskRepository.find).toHaveBeenCalledWith({
|
||||||
|
where: { _id: new ObjectId(mockTaskId) },
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockTask);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return null if task is not found', async () => {
|
||||||
|
const mockTaskId = new ObjectId().toString();
|
||||||
|
|
||||||
|
jest.spyOn(downloadTaskRepository, 'find').mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await service.getDownloadTaskById({ taskId: mockTaskId });
|
||||||
|
|
||||||
|
expect(result).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteDownloadTask', () => {
|
||||||
|
it('should update task status to REMOVED', async () => {
|
||||||
|
const mockTaskId = new ObjectId().toString();
|
||||||
|
const mockUpdateResult = { matchedCount: 1 };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(downloadTaskRepository, 'updateOne')
|
||||||
|
.mockResolvedValue(mockUpdateResult as any);
|
||||||
|
|
||||||
|
const result = await service.deleteDownloadTask({ taskId: mockTaskId });
|
||||||
|
|
||||||
|
expect(downloadTaskRepository.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(mockTaskId),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: expect.any(Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$push: { statusList: expect.any(Object) },
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual(mockUpdateResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -21,8 +21,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
code: {
|
code: {
|
||||||
bannerConf: {
|
bannerConf: {
|
||||||
titleConfig: {
|
titleConfig: {
|
||||||
mainTitle:
|
mainTitle: '<h3 style="text-align: center">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>',
|
||||||
'<h3 style="text-align: center">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>',
|
|
||||||
subTitle: '',
|
subTitle: '',
|
||||||
},
|
},
|
||||||
bannerConfig: {
|
bannerConfig: {
|
||||||
@ -32,7 +31,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
begTime: '2024-03-14 14:54:41',
|
beginTime: '2024-03-14 14:54:41',
|
||||||
endTime: '2034-03-14 14:54:41',
|
endTime: '2034-03-14 14:54:41',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
tLimit: 0,
|
tLimit: 0,
|
||||||
@ -44,6 +43,17 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
logoImageWidth: '60%',
|
logoImageWidth: '60%',
|
||||||
},
|
},
|
||||||
skinConf: {
|
skinConf: {
|
||||||
|
backgroundConf: {
|
||||||
|
color: '#fff',
|
||||||
|
type: 'color',
|
||||||
|
image: '',
|
||||||
|
},
|
||||||
|
themeConf: {
|
||||||
|
color: '#ffa600',
|
||||||
|
},
|
||||||
|
contentConf: {
|
||||||
|
opacity: 100,
|
||||||
|
},
|
||||||
skinColor: '#4a4c5b',
|
skinColor: '#4a4c5b',
|
||||||
inputBgColor: '#ffffff',
|
inputBgColor: '#ffffff',
|
||||||
},
|
},
|
||||||
@ -284,7 +294,7 @@ export const mockSensitiveResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageId: '65f29f3192862d6a9067ad1c',
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
} as ResponseSchema;
|
} as unknown as ResponseSchema;
|
||||||
|
|
||||||
export const mockResponseSchema: ResponseSchema = {
|
export const mockResponseSchema: ResponseSchema = {
|
||||||
_id: new ObjectId('65b0d46e04d5db18534c0f7c'),
|
_id: new ObjectId('65b0d46e04d5db18534c0f7c'),
|
||||||
@ -303,19 +313,17 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
code: {
|
code: {
|
||||||
bannerConf: {
|
bannerConf: {
|
||||||
titleConfig: {
|
titleConfig: {
|
||||||
mainTitle:
|
mainTitle: '<h3 style="text-align: center">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>',
|
||||||
'<h3 style="text-align: center">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>',
|
|
||||||
subTitle: '',
|
subTitle: '',
|
||||||
},
|
},
|
||||||
bannerConfig: {
|
bannerConfig: {
|
||||||
bgImage:
|
bgImage: 'http://10.190.55.101:3000/imgs/skin/17e06b7604a007e1d3e1453b9ddadc3c.webp',
|
||||||
'http://10.190.55.101:3000/imgs/skin/17e06b7604a007e1d3e1453b9ddadc3c.webp',
|
|
||||||
videoLink: '',
|
videoLink: '',
|
||||||
postImg: '',
|
postImg: '',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
begTime: '2024-01-23 21:59:05',
|
beginTime: '2024-01-23 21:59:05',
|
||||||
endTime: '2034-01-23 21:59:05',
|
endTime: '2034-01-23 21:59:05',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
tLimit: 0,
|
tLimit: 0,
|
||||||
@ -327,6 +335,17 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
logoImageWidth: '60%',
|
logoImageWidth: '60%',
|
||||||
},
|
},
|
||||||
skinConf: {
|
skinConf: {
|
||||||
|
backgroundConf: {
|
||||||
|
color: '#fff',
|
||||||
|
type: 'color',
|
||||||
|
image: '',
|
||||||
|
},
|
||||||
|
themeConf: {
|
||||||
|
color: '#ffa600',
|
||||||
|
},
|
||||||
|
contentConf: {
|
||||||
|
opacity: 100,
|
||||||
|
},
|
||||||
skinColor: '#4a4c5b',
|
skinColor: '#4a4c5b',
|
||||||
inputBgColor: '#ffffff',
|
inputBgColor: '#ffffff',
|
||||||
},
|
},
|
||||||
@ -634,4 +653,4 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
pageId: '65afc62904d5db18534c0f78',
|
pageId: '65afc62904d5db18534c0f78',
|
||||||
createDate: 1710340841289,
|
createDate: 1710340841289,
|
||||||
updateDate: 1710340841289.0,
|
updateDate: 1710340841289.0,
|
||||||
} as ResponseSchema;
|
} as unknown as ResponseSchema;
|
||||||
|
87
server/src/modules/survey/__test/session.controller.spec.ts
Normal file
87
server/src/modules/survey/__test/session.controller.spec.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { SessionController } from '../controllers/session.controller';
|
||||||
|
import { SessionService } from '../services/session.service';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SessionGuard } from 'src/guards/session.guard';
|
||||||
|
|
||||||
|
describe('SessionController', () => {
|
||||||
|
let controller: SessionController;
|
||||||
|
let sessionService: jest.Mocked<SessionService>;
|
||||||
|
let logger: jest.Mocked<Logger>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [SessionController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: SessionService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
updateSessionToEditing: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.overrideGuard(Authentication)
|
||||||
|
.useValue({ canActivate: () => true })
|
||||||
|
.overrideGuard(SurveyGuard)
|
||||||
|
.useValue({ canActivate: () => true })
|
||||||
|
.overrideGuard(SessionGuard)
|
||||||
|
.useValue({ canActivate: () => true })
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
controller = module.get<SessionController>(SessionController);
|
||||||
|
sessionService = module.get<jest.Mocked<SessionService>>(SessionService);
|
||||||
|
logger = module.get<jest.Mocked<Logger>>(Logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should create a session', async () => {
|
||||||
|
const reqBody = { surveyId: '123' };
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
const session: any = { _id: 'sessionId' };
|
||||||
|
|
||||||
|
sessionService.create.mockResolvedValue(session);
|
||||||
|
|
||||||
|
const result = await controller.create(reqBody, req);
|
||||||
|
|
||||||
|
expect(sessionService.create).toHaveBeenCalledWith({
|
||||||
|
surveyId: '123',
|
||||||
|
userId: 'userId',
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ code: 200, data: { sessionId: 'sessionId' } });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const reqBody = { surveyId: null };
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.create(reqBody, req);
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
|
expect(logger.error).toHaveBeenCalled();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should seize a session', async () => {
|
||||||
|
const req = {
|
||||||
|
sessionInfo: { _id: 'sessionId', surveyId: 'surveyId' },
|
||||||
|
};
|
||||||
|
|
||||||
|
await controller.seize(req);
|
||||||
|
|
||||||
|
expect(sessionService.updateSessionToEditing).toHaveBeenCalledWith({
|
||||||
|
sessionId: 'sessionId',
|
||||||
|
surveyId: 'surveyId',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -5,18 +5,22 @@ import { SurveyConfService } from '../services/surveyConf.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
import { ContentSecurityService } from '../services/contentSecurity.service';
|
import { ContentSecurityService } from '../services/contentSecurity.service';
|
||||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||||
|
import { CounterService } from '../../surveyResponse/services/counter.service';
|
||||||
|
import { SessionService } from '../services/session.service';
|
||||||
|
import { UserService } from '../../auth/services/user.service';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { SurveyConf } from 'src/models/surveyConf.entity';
|
import { SurveyConf } from 'src/models/surveyConf.entity';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { Logger } from 'src/logger';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
|
||||||
import { LoggerProvider } from 'src/logger/logger.provider';
|
|
||||||
|
|
||||||
jest.mock('../services/surveyMeta.service');
|
jest.mock('../services/surveyMeta.service');
|
||||||
jest.mock('../services/surveyConf.service');
|
jest.mock('../services/surveyConf.service');
|
||||||
jest.mock('../../surveyResponse/services/responseScheme.service');
|
jest.mock('../../surveyResponse/services/responseScheme.service');
|
||||||
jest.mock('../services/contentSecurity.service');
|
jest.mock('../services/contentSecurity.service');
|
||||||
jest.mock('../services/surveyHistory.service');
|
jest.mock('../services/surveyHistory.service');
|
||||||
|
jest.mock('../services/session.service');
|
||||||
|
jest.mock('../../surveyResponse/services/counter.service');
|
||||||
|
jest.mock('../../auth/services/user.service');
|
||||||
|
|
||||||
jest.mock('src/guards/authentication.guard');
|
jest.mock('src/guards/authentication.guard');
|
||||||
jest.mock('src/guards/survey.guard');
|
jest.mock('src/guards/survey.guard');
|
||||||
@ -27,19 +31,36 @@ describe('SurveyController', () => {
|
|||||||
let surveyMetaService: SurveyMetaService;
|
let surveyMetaService: SurveyMetaService;
|
||||||
let surveyConfService: SurveyConfService;
|
let surveyConfService: SurveyConfService;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
let contentSecurityService: ContentSecurityService;
|
|
||||||
let surveyHistoryService: SurveyHistoryService;
|
let surveyHistoryService: SurveyHistoryService;
|
||||||
|
let sessionService: SessionService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
controllers: [SurveyController],
|
controllers: [SurveyController],
|
||||||
providers: [
|
providers: [
|
||||||
SurveyMetaService,
|
SurveyMetaService,
|
||||||
SurveyConfService,
|
{
|
||||||
|
provide: SurveyConfService,
|
||||||
|
useValue: {
|
||||||
|
getSurveyConfBySurveyId: jest.fn(),
|
||||||
|
getSurveyContentByCode: jest.fn(),
|
||||||
|
createSurveyConf: jest.fn(),
|
||||||
|
saveSurveyConf: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
ContentSecurityService,
|
ContentSecurityService,
|
||||||
SurveyHistoryService,
|
SurveyHistoryService,
|
||||||
LoggerProvider,
|
SessionService,
|
||||||
|
CounterService,
|
||||||
|
UserService,
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
@ -49,17 +70,14 @@ describe('SurveyController', () => {
|
|||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
contentSecurityService = module.get<ContentSecurityService>(
|
|
||||||
ContentSecurityService,
|
|
||||||
);
|
|
||||||
surveyHistoryService =
|
surveyHistoryService =
|
||||||
module.get<SurveyHistoryService>(SurveyHistoryService);
|
module.get<SurveyHistoryService>(SurveyHistoryService);
|
||||||
|
sessionService = module.get<SessionService>(SessionService);
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('getBannerData', () => {
|
describe('getBannerData', () => {
|
||||||
it('should return banner data', async () => {
|
it('should return banner data', async () => {
|
||||||
const result = await controller.getBannerData();
|
const result = await controller.getBannerData();
|
||||||
|
|
||||||
expect(result.code).toBe(200);
|
expect(result.code).toBe(200);
|
||||||
expect(result.data).toBeDefined();
|
expect(result.data).toBeDefined();
|
||||||
});
|
});
|
||||||
@ -71,33 +89,17 @@ describe('SurveyController', () => {
|
|||||||
surveyType: 'normal',
|
surveyType: 'normal',
|
||||||
remark: '问卷调研',
|
remark: '问卷调研',
|
||||||
title: '问卷调研',
|
title: '问卷调研',
|
||||||
} as SurveyMeta;
|
};
|
||||||
|
|
||||||
const newId = new ObjectId();
|
const newId = new ObjectId();
|
||||||
jest
|
jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
|
||||||
.spyOn(surveyMetaService, 'createSurveyMeta')
|
_id: newId,
|
||||||
.mockImplementation(() => {
|
} as SurveyMeta);
|
||||||
const result = {
|
|
||||||
_id: newId,
|
jest.spyOn(surveyConfService, 'createSurveyConf').mockResolvedValue({
|
||||||
} as SurveyMeta;
|
_id: new ObjectId(),
|
||||||
return Promise.resolve(result);
|
pageId: newId.toString(),
|
||||||
});
|
} as SurveyConf);
|
||||||
jest
|
|
||||||
.spyOn(surveyConfService, 'createSurveyConf')
|
|
||||||
.mockImplementation(
|
|
||||||
(params: {
|
|
||||||
surveyId: string;
|
|
||||||
surveyType: string;
|
|
||||||
createMethod: string;
|
|
||||||
createFrom: string;
|
|
||||||
}) => {
|
|
||||||
const result = {
|
|
||||||
_id: new ObjectId(),
|
|
||||||
pageId: params.surveyId,
|
|
||||||
code: {},
|
|
||||||
} as SurveyConf;
|
|
||||||
return Promise.resolve(result);
|
|
||||||
},
|
|
||||||
);
|
|
||||||
|
|
||||||
const result = await controller.createSurvey(surveyInfo, {
|
const result = await controller.createSurvey(surveyInfo, {
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
@ -126,19 +128,15 @@ describe('SurveyController', () => {
|
|||||||
createFrom: existsSurveyId.toString(),
|
createFrom: existsSurveyId.toString(),
|
||||||
};
|
};
|
||||||
|
|
||||||
jest
|
jest.spyOn(surveyMetaService, 'createSurveyMeta').mockResolvedValue({
|
||||||
.spyOn(surveyMetaService, 'createSurveyMeta')
|
_id: new ObjectId(),
|
||||||
.mockImplementation(() => {
|
} as SurveyMeta);
|
||||||
const result = {
|
|
||||||
_id: new ObjectId(),
|
|
||||||
} as SurveyMeta;
|
|
||||||
return Promise.resolve(result);
|
|
||||||
});
|
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
surveyMeta: existsSurveyMeta,
|
surveyMeta: existsSurveyMeta,
|
||||||
}; // 模拟请求对象,根据实际情况进行调整
|
};
|
||||||
|
|
||||||
const result = await controller.createSurvey(params, request);
|
const result = await controller.createSurvey(params, request);
|
||||||
expect(result?.data?.id).toBeDefined();
|
expect(result?.data?.id).toBeDefined();
|
||||||
});
|
});
|
||||||
@ -159,6 +157,12 @@ describe('SurveyController', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(surveyHistoryService, 'addHistory')
|
.spyOn(surveyHistoryService, 'addHistory')
|
||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
|
jest
|
||||||
|
.spyOn(sessionService, 'findLatestEditingOne')
|
||||||
|
.mockResolvedValue(null);
|
||||||
|
jest
|
||||||
|
.spyOn(sessionService, 'updateSessionToEditing')
|
||||||
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const reqBody = {
|
const reqBody = {
|
||||||
surveyId: surveyId.toString(),
|
surveyId: surveyId.toString(),
|
||||||
@ -168,16 +172,31 @@ describe('SurveyController', () => {
|
|||||||
bannerConfig: {},
|
bannerConfig: {},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
begTime: '2024-01-23 21:59:05',
|
beginTime: '2024-01-23 21:59:05',
|
||||||
endTime: '2034-01-23 21:59:05',
|
endTime: '2034-01-23 21:59:05',
|
||||||
},
|
},
|
||||||
bottomConf: { logoImage: '/imgs/Logo.webp', logoImageWidth: '60%' },
|
bottomConf: { logoImage: '/imgs/Logo.webp', logoImageWidth: '60%' },
|
||||||
skinConf: { skinColor: '#4a4c5b', inputBgColor: '#ffffff' },
|
skinConf: {
|
||||||
|
skinColor: '#4a4c5b',
|
||||||
|
inputBgColor: '#ffffff',
|
||||||
|
backgroundConf: {
|
||||||
|
color: '#fff',
|
||||||
|
type: 'color',
|
||||||
|
image: '',
|
||||||
|
},
|
||||||
|
themeConf: {
|
||||||
|
color: '#ffa600',
|
||||||
|
},
|
||||||
|
contentConf: {
|
||||||
|
opacity: 100,
|
||||||
|
},
|
||||||
|
},
|
||||||
submitConf: {},
|
submitConf: {},
|
||||||
dataConf: {
|
dataConf: {
|
||||||
dataList: [],
|
dataList: [],
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
sessionId: 'mock-session-id',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = await controller.updateConf(reqBody, {
|
const result = await controller.updateConf(reqBody, {
|
||||||
@ -229,12 +248,10 @@ describe('SurveyController', () => {
|
|||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue(
|
.mockResolvedValue({
|
||||||
Promise.resolve({
|
_id: new ObjectId(),
|
||||||
_id: new ObjectId(),
|
pageId: surveyId.toString(),
|
||||||
pageId: surveyId.toString(),
|
} as SurveyConf);
|
||||||
} as SurveyConf),
|
|
||||||
);
|
|
||||||
|
|
||||||
const request = {
|
const request = {
|
||||||
user: { username: 'testUser', _id: new ObjectId() },
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
@ -250,7 +267,7 @@ describe('SurveyController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
describe('publishSurvey', () => {
|
describe('publishSurvey', () => {
|
||||||
it('should publish a survey success', async () => {
|
it('should publish a survey successfully', async () => {
|
||||||
const surveyId = new ObjectId();
|
const surveyId = new ObjectId();
|
||||||
const surveyMeta = {
|
const surveyMeta = {
|
||||||
_id: surveyId,
|
_id: surveyId,
|
||||||
@ -260,80 +277,24 @@ describe('SurveyController', () => {
|
|||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue(
|
.mockResolvedValue({
|
||||||
Promise.resolve({
|
_id: new ObjectId(),
|
||||||
_id: new ObjectId(),
|
pageId: surveyId.toString(),
|
||||||
pageId: surveyId.toString(),
|
code: {},
|
||||||
} as SurveyConf),
|
} as SurveyConf);
|
||||||
);
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyContentByCode')
|
.spyOn(surveyConfService, 'getSurveyContentByCode')
|
||||||
.mockResolvedValue({
|
.mockResolvedValue({ text: '' });
|
||||||
text: '题目1',
|
|
||||||
});
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(contentSecurityService, 'isForbiddenContent')
|
|
||||||
.mockResolvedValue(false);
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'publishSurveyMeta')
|
|
||||||
.mockResolvedValue(undefined);
|
|
||||||
jest
|
|
||||||
.spyOn(responseSchemaService, 'publishResponseSchema')
|
|
||||||
.mockResolvedValue(undefined);
|
|
||||||
jest
|
|
||||||
.spyOn(surveyHistoryService, 'addHistory')
|
|
||||||
.mockResolvedValue(undefined);
|
|
||||||
|
|
||||||
const result = await controller.publishSurvey(
|
const result = await controller.publishSurvey(
|
||||||
{ surveyId: surveyId.toString() },
|
{ surveyId: surveyId.toString() },
|
||||||
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
{
|
||||||
);
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
|
surveyMeta,
|
||||||
expect(result).toEqual({
|
},
|
||||||
code: 200,
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should not publish a survey with forbidden content', async () => {
|
|
||||||
const surveyId = new ObjectId();
|
|
||||||
const surveyMeta = {
|
|
||||||
_id: surveyId,
|
|
||||||
surveyType: 'normal',
|
|
||||||
owner: 'testUser',
|
|
||||||
} as SurveyMeta;
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
|
||||||
.mockResolvedValue(
|
|
||||||
Promise.resolve({
|
|
||||||
_id: new ObjectId(),
|
|
||||||
pageId: surveyId.toString(),
|
|
||||||
} as SurveyConf),
|
|
||||||
);
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyConfService, 'getSurveyContentByCode')
|
|
||||||
.mockResolvedValue({
|
|
||||||
text: '违禁词',
|
|
||||||
});
|
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(contentSecurityService, 'isForbiddenContent')
|
|
||||||
.mockResolvedValue(true);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
controller.publishSurvey(
|
|
||||||
{ surveyId: surveyId.toString() },
|
|
||||||
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
|
||||||
),
|
|
||||||
).rejects.toThrow(
|
|
||||||
new HttpException(
|
|
||||||
'问卷存在非法关键字,不允许发布',
|
|
||||||
EXCEPTION_CODE.SURVEY_CONTENT_NOT_ALLOW,
|
|
||||||
),
|
|
||||||
);
|
);
|
||||||
|
expect(result.code).toBe(200);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -66,7 +66,7 @@ describe('SurveyHistoryController', () => {
|
|||||||
it('should return history list when query is valid', async () => {
|
it('should return history list when query is valid', async () => {
|
||||||
const queryInfo = { surveyId: 'survey123', historyType: 'published' };
|
const queryInfo = { surveyId: 'survey123', historyType: 'published' };
|
||||||
|
|
||||||
await controller.getList(queryInfo, {});
|
await controller.getList(queryInfo);
|
||||||
|
|
||||||
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledWith({
|
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledWith({
|
||||||
surveyId: queryInfo.surveyId,
|
surveyId: queryInfo.surveyId,
|
||||||
|
@ -42,7 +42,7 @@ describe('SurveyHistoryService', () => {
|
|||||||
msgContent: undefined,
|
msgContent: undefined,
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
begTime: '',
|
beginTime: '',
|
||||||
endTime: '',
|
endTime: '',
|
||||||
answerBegTime: '',
|
answerBegTime: '',
|
||||||
answerEndTime: '',
|
answerEndTime: '',
|
||||||
@ -78,7 +78,12 @@ describe('SurveyHistoryService', () => {
|
|||||||
.spyOn(repository, 'save')
|
.spyOn(repository, 'save')
|
||||||
.mockResolvedValueOnce({} as SurveyHistory);
|
.mockResolvedValueOnce({} as SurveyHistory);
|
||||||
|
|
||||||
await service.addHistory({ surveyId, schema, type, user });
|
await service.addHistory({
|
||||||
|
surveyId,
|
||||||
|
schema,
|
||||||
|
type,
|
||||||
|
user,
|
||||||
|
});
|
||||||
|
|
||||||
expect(spyCreate).toHaveBeenCalledWith({
|
expect(spyCreate).toHaveBeenCalledWith({
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
import { LoggerProvider } from 'src/logger/logger.provider';
|
import { Logger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { CollaboratorService } from '../services/collaborator.service';
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
@ -28,7 +28,12 @@ describe('SurveyMetaController', () => {
|
|||||||
.mockResolvedValue({ count: 0, data: [] }),
|
.mockResolvedValue({ count: 0, data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
LoggerProvider,
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error() {},
|
||||||
|
},
|
||||||
|
},
|
||||||
{
|
{
|
||||||
provide: CollaboratorService,
|
provide: CollaboratorService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -119,6 +124,7 @@ describe('SurveyMetaController', () => {
|
|||||||
subStatus: {
|
subStatus: {
|
||||||
date: date,
|
date: date,
|
||||||
},
|
},
|
||||||
|
surveyType: 'normal',
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
@ -145,10 +151,12 @@ describe('SurveyMetaController', () => {
|
|||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
),
|
),
|
||||||
}),
|
}),
|
||||||
|
surveyType: 'normal',
|
||||||
}),
|
}),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
||||||
pageNum: queryInfo.curPage,
|
pageNum: queryInfo.curPage,
|
||||||
pageSize: queryInfo.pageSize,
|
pageSize: queryInfo.pageSize,
|
||||||
@ -199,4 +207,24 @@ describe('SurveyMetaController', () => {
|
|||||||
workspaceId: undefined,
|
workspaceId: undefined,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle Joi validation in getList', async () => {
|
||||||
|
const invalidQueryInfo: any = {
|
||||||
|
curPage: 'invalid',
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
username: 'test-user',
|
||||||
|
_id: new ObjectId(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
try {
|
||||||
|
await controller.getList(invalidQueryInfo, req);
|
||||||
|
} catch (error) {
|
||||||
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
|
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,7 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
|
|||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
@ -13,7 +13,7 @@ import { ObjectId } from 'mongodb';
|
|||||||
describe('SurveyMetaService', () => {
|
describe('SurveyMetaService', () => {
|
||||||
let service: SurveyMetaService;
|
let service: SurveyMetaService;
|
||||||
let surveyRepository: MongoRepository<SurveyMeta>;
|
let surveyRepository: MongoRepository<SurveyMeta>;
|
||||||
let pluginManager: XiaojuSurveyPluginManager;
|
let pluginManager: PluginManager;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -37,9 +37,7 @@ describe('SurveyMetaService', () => {
|
|||||||
surveyRepository = module.get<MongoRepository<SurveyMeta>>(
|
surveyRepository = module.get<MongoRepository<SurveyMeta>>(
|
||||||
getRepositoryToken(SurveyMeta),
|
getRepositoryToken(SurveyMeta),
|
||||||
);
|
);
|
||||||
pluginManager = module.get<XiaojuSurveyPluginManager>(
|
pluginManager = module.get<PluginManager>(PluginManager);
|
||||||
XiaojuSurveyPluginManager,
|
|
||||||
);
|
|
||||||
pluginManager.registerPlugin(new SurveyUtilPlugin());
|
pluginManager.registerPlugin(new SurveyUtilPlugin());
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -109,9 +107,9 @@ describe('SurveyMetaService', () => {
|
|||||||
|
|
||||||
const result = await service.editSurveyMeta(survey);
|
const result = await service.editSurveyMeta(survey);
|
||||||
|
|
||||||
expect(survey.subStatus.status).toEqual(RECORD_SUB_STATUS.EDITING);
|
expect(survey.curStatus.status).toEqual(RECORD_STATUS.EDITING);
|
||||||
expect(survey.statusList.length).toBe(1);
|
expect(survey.statusList.length).toBe(1);
|
||||||
expect(survey.statusList[0].status).toEqual(RECORD_SUB_STATUS.EDITING);
|
expect(survey.statusList[0].status).toEqual(RECORD_STATUS.EDITING);
|
||||||
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
||||||
expect(result).toEqual(survey);
|
expect(result).toEqual(survey);
|
||||||
});
|
});
|
||||||
@ -136,9 +134,9 @@ describe('SurveyMetaService', () => {
|
|||||||
|
|
||||||
// 验证结果
|
// 验证结果
|
||||||
expect(result).toBe(survey);
|
expect(result).toBe(survey);
|
||||||
expect(survey.subStatus.status).toBe(RECORD_SUB_STATUS.REMOVED);
|
expect(survey.subStatus.status).toBe(RECORD_STATUS.REMOVED);
|
||||||
expect(survey.statusList.length).toBe(1);
|
expect(survey.statusList.length).toBe(1);
|
||||||
expect(survey.statusList[0].status).toBe(RECORD_SUB_STATUS.REMOVED);
|
expect(survey.statusList[0].status).toBe(RECORD_STATUS.REMOVED);
|
||||||
expect(surveyRepository.save).toHaveBeenCalledTimes(1);
|
expect(surveyRepository.save).toHaveBeenCalledTimes(1);
|
||||||
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
expect(surveyRepository.save).toHaveBeenCalledWith(survey);
|
||||||
});
|
});
|
||||||
@ -146,8 +144,8 @@ describe('SurveyMetaService', () => {
|
|||||||
it('should throw exception when survey is already removed', async () => {
|
it('should throw exception when survey is already removed', async () => {
|
||||||
// 准备假的SurveyMeta对象,其状态已设置为REMOVED
|
// 准备假的SurveyMeta对象,其状态已设置为REMOVED
|
||||||
const survey = new SurveyMeta();
|
const survey = new SurveyMeta();
|
||||||
survey.subStatus = {
|
survey.curStatus = {
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
status: RECORD_STATUS.REMOVED,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -69,7 +69,7 @@ export class CollaboratorController {
|
|||||||
) {
|
) {
|
||||||
const { error, value } = CreateCollaboratorDto.validate(reqBody);
|
const { error, value } = CreateCollaboratorDto.validate(reqBody);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'系统错误,请联系管理员',
|
'系统错误,请联系管理员',
|
||||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
@ -124,7 +124,7 @@ export class CollaboratorController {
|
|||||||
) {
|
) {
|
||||||
const { error, value } = BatchSaveCollaboratorDto.validate(reqBody);
|
const { error, value } = BatchSaveCollaboratorDto.validate(reqBody);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'系统错误,请联系管理员',
|
'系统错误,请联系管理员',
|
||||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
@ -184,11 +184,15 @@ export class CollaboratorController {
|
|||||||
neIdList: collaboratorIdList,
|
neIdList: collaboratorIdList,
|
||||||
userIdList: newCollaboratorUserIdList,
|
userIdList: newCollaboratorUserIdList,
|
||||||
});
|
});
|
||||||
this.logger.info('batchDelete:' + JSON.stringify(delRes), { req });
|
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) {
|
if (Array.isArray(newCollaborator) && newCollaborator.length > 0) {
|
||||||
const insertRes = await this.collaboratorService.batchCreate({
|
const insertRes = await this.collaboratorService.batchCreate({
|
||||||
surveyId: value.surveyId,
|
surveyId: value.surveyId,
|
||||||
collaboratorList: newCollaborator,
|
collaboratorList: newCollaborator,
|
||||||
|
creator: username,
|
||||||
|
creatorId: userId,
|
||||||
});
|
});
|
||||||
this.logger.info(`${JSON.stringify(insertRes)}`);
|
this.logger.info(`${JSON.stringify(insertRes)}`);
|
||||||
}
|
}
|
||||||
@ -198,6 +202,8 @@ export class CollaboratorController {
|
|||||||
this.collaboratorService.updateById({
|
this.collaboratorService.updateById({
|
||||||
collaboratorId: item._id,
|
collaboratorId: item._id,
|
||||||
permissions: item.permissions,
|
permissions: item.permissions,
|
||||||
|
operator: username,
|
||||||
|
operatorId: userId,
|
||||||
}),
|
}),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
@ -208,7 +214,7 @@ export class CollaboratorController {
|
|||||||
const delRes = await this.collaboratorService.batchDeleteBySurveyId(
|
const delRes = await this.collaboratorService.batchDeleteBySurveyId(
|
||||||
value.surveyId,
|
value.surveyId,
|
||||||
);
|
);
|
||||||
this.logger.info(JSON.stringify(delRes), { req });
|
this.logger.info(JSON.stringify(delRes));
|
||||||
}
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -225,11 +231,10 @@ export class CollaboratorController {
|
|||||||
])
|
])
|
||||||
async getSurveyCollaboratorList(
|
async getSurveyCollaboratorList(
|
||||||
@Query() query: GetSurveyCollaboratorListDto,
|
@Query() query: GetSurveyCollaboratorListDto,
|
||||||
@Request() req,
|
|
||||||
) {
|
) {
|
||||||
const { error, value } = GetSurveyCollaboratorListDto.validate(query);
|
const { error, value } = GetSurveyCollaboratorListDto.validate(query);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -263,17 +268,14 @@ export class CollaboratorController {
|
|||||||
@SetMetadata('surveyPermission', [
|
@SetMetadata('surveyPermission', [
|
||||||
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
])
|
])
|
||||||
async changeUserPermission(
|
async changeUserPermission(@Body() reqBody: ChangeUserPermissionDto) {
|
||||||
@Body() reqBody: ChangeUserPermissionDto,
|
|
||||||
@Request() req,
|
|
||||||
) {
|
|
||||||
const { error, value } = Joi.object({
|
const { error, value } = Joi.object({
|
||||||
surveyId: Joi.string(),
|
surveyId: Joi.string(),
|
||||||
userId: Joi.string(),
|
userId: Joi.string(),
|
||||||
permissions: Joi.array().items(Joi.string().required()),
|
permissions: Joi.array().items(Joi.string().required()),
|
||||||
}).validate(reqBody);
|
}).validate(reqBody);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,13 +294,13 @@ export class CollaboratorController {
|
|||||||
@SetMetadata('surveyPermission', [
|
@SetMetadata('surveyPermission', [
|
||||||
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
])
|
])
|
||||||
async deleteCollaborator(@Query() query, @Request() req) {
|
async deleteCollaborator(@Query() query) {
|
||||||
const { error, value } = Joi.object({
|
const { error, value } = Joi.object({
|
||||||
surveyId: Joi.string(),
|
surveyId: Joi.string(),
|
||||||
userId: Joi.string(),
|
userId: Joi.string(),
|
||||||
}).validate(query);
|
}).validate(query);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -319,7 +321,7 @@ export class CollaboratorController {
|
|||||||
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
|
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
|
||||||
|
|
||||||
if (!surveyMeta) {
|
if (!surveyMeta) {
|
||||||
this.logger.error(`问卷不存在: ${surveyId}`, { req });
|
this.logger.error(`问卷不存在: ${surveyId}`);
|
||||||
throw new HttpException('问卷不存在', EXCEPTION_CODE.SURVEY_NOT_FOUND);
|
throw new HttpException('问卷不存在', EXCEPTION_CODE.SURVEY_NOT_FOUND);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
SetMetadata,
|
SetMetadata,
|
||||||
Request,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
@ -14,7 +13,7 @@ import { DataStatisticService } from '../services/dataStatistic.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
|
||||||
import { Authentication } from 'src/guards/authentication.guard';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { SurveyGuard } from 'src/guards/survey.guard';
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
@ -31,7 +30,7 @@ export class DataStatisticController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly dataStatisticService: DataStatisticService,
|
private readonly dataStatisticService: DataStatisticService,
|
||||||
private readonly pluginManager: XiaojuSurveyPluginManager,
|
private readonly pluginManager: PluginManager,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@ -44,19 +43,18 @@ export class DataStatisticController {
|
|||||||
async data(
|
async data(
|
||||||
@Query()
|
@Query()
|
||||||
queryInfo,
|
queryInfo,
|
||||||
@Request() req,
|
|
||||||
) {
|
) {
|
||||||
const { value, error } = await Joi.object({
|
const { value, error } = await Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
isDesensitive: Joi.boolean().default(true), // 默认true就是需要脱敏
|
isMasked: Joi.boolean().default(true), // 默认true就是需要脱敏
|
||||||
page: Joi.number().default(1),
|
page: Joi.number().default(1),
|
||||||
pageSize: Joi.number().default(10),
|
pageSize: Joi.number().default(10),
|
||||||
}).validate(queryInfo);
|
}).validate(queryInfo);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const { surveyId, isDesensitive, page, pageSize } = value;
|
const { surveyId, isMasked, page, pageSize } = value;
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
const { total, listHead, listBody } =
|
const { total, listHead, listBody } =
|
||||||
@ -67,10 +65,10 @@ export class DataStatisticController {
|
|||||||
pageSize,
|
pageSize,
|
||||||
});
|
});
|
||||||
|
|
||||||
if (isDesensitive) {
|
if (isMasked) {
|
||||||
// 脱敏
|
// 脱敏
|
||||||
listBody.forEach((item) => {
|
listBody.forEach((item) => {
|
||||||
this.pluginManager.triggerHook('desensitiveData', item);
|
this.pluginManager.triggerHook('maskData', item);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
189
server/src/modules/survey/controllers/downloadTask.controller.ts
Normal file
189
server/src/modules/survey/controllers/downloadTask.controller.ts
Normal file
@ -0,0 +1,189 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Query,
|
||||||
|
HttpCode,
|
||||||
|
UseGuards,
|
||||||
|
SetMetadata,
|
||||||
|
Request,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { DownloadTaskService } from '../services/downloadTask.service';
|
||||||
|
import {
|
||||||
|
GetDownloadTaskDto,
|
||||||
|
CreateDownloadDto,
|
||||||
|
GetDownloadTaskListDto,
|
||||||
|
DeleteDownloadTaskDto,
|
||||||
|
} from '../dto/downloadTask.dto';
|
||||||
|
import moment from 'moment';
|
||||||
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
|
||||||
|
@ApiTags('downloadTask')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller('/api/downloadTask')
|
||||||
|
export class DownloadTaskController {
|
||||||
|
constructor(
|
||||||
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
|
private readonly downloadTaskService: DownloadTaskService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('/createTask')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async createTask(
|
||||||
|
@Body()
|
||||||
|
reqBody: CreateDownloadDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { value, error } = CreateDownloadDto.validate(reqBody);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message);
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
const { surveyId, isMasked } = value;
|
||||||
|
const responseSchema =
|
||||||
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
|
const id = await this.downloadTaskService.createDownloadTask({
|
||||||
|
surveyId,
|
||||||
|
responseSchema,
|
||||||
|
creatorId: req.user._id.toString(),
|
||||||
|
creator: req.user.username,
|
||||||
|
params: { isMasked },
|
||||||
|
});
|
||||||
|
this.downloadTaskService.processDownloadTask({ taskId: id });
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: { taskId: id },
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/getDownloadTaskList')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async downloadList(
|
||||||
|
@Query()
|
||||||
|
queryInfo: GetDownloadTaskListDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { value, error } = GetDownloadTaskListDto.validate(queryInfo);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message);
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
const { pageIndex, pageSize } = value;
|
||||||
|
const { total, list } = await this.downloadTaskService.getDownloadTaskList({
|
||||||
|
creatorId: req.user._id.toString(),
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
total: total,
|
||||||
|
list: list.map((data) => {
|
||||||
|
const item: Record<string, any> = {};
|
||||||
|
item.taskId = data._id.toString();
|
||||||
|
item.status = data.status;
|
||||||
|
item.filename = data.filename;
|
||||||
|
item.url = data.url;
|
||||||
|
const fmt = 'YYYY-MM-DD HH:mm:ss';
|
||||||
|
const units = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
|
||||||
|
let unitIndex = 0;
|
||||||
|
let size = Number(data.fileSize);
|
||||||
|
if (isNaN(size)) {
|
||||||
|
item.fileSize = data.fileSize;
|
||||||
|
} else {
|
||||||
|
while (size >= 1024 && unitIndex < units.length - 1) {
|
||||||
|
size /= 1024;
|
||||||
|
unitIndex++;
|
||||||
|
}
|
||||||
|
item.fileSize = `${size.toFixed()} ${units[unitIndex]}`;
|
||||||
|
}
|
||||||
|
item.createdAt = moment(data.createdAt).format(fmt);
|
||||||
|
return item;
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('/getDownloadTask')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async getDownloadTask(@Query() query: GetDownloadTaskDto, @Request() req) {
|
||||||
|
const { value, error } = GetDownloadTaskDto.validate(query);
|
||||||
|
if (error) {
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskInfo = await this.downloadTaskService.getDownloadTaskById({
|
||||||
|
taskId: value.taskId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!taskInfo) {
|
||||||
|
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskInfo.creatorId !== req.user._id.toString()) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
const res: Record<string, any> = {
|
||||||
|
...taskInfo,
|
||||||
|
};
|
||||||
|
res.taskId = taskInfo._id.toString();
|
||||||
|
delete res._id;
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: res,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/deleteDownloadTask')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async deleteFileByName(@Body() body: DeleteDownloadTaskDto, @Request() req) {
|
||||||
|
const { value, error } = DeleteDownloadTaskDto.validate(body);
|
||||||
|
if (error) {
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
const { taskId } = value;
|
||||||
|
|
||||||
|
const taskInfo = await this.downloadTaskService.getDownloadTaskById({
|
||||||
|
taskId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!taskInfo) {
|
||||||
|
throw new HttpException('任务不存在', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (taskInfo.creatorId !== req.user._id.toString()) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
const delRes = await this.downloadTaskService.deleteDownloadTask({
|
||||||
|
taskId,
|
||||||
|
operator: req.user.username,
|
||||||
|
operatorId: req.user._id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: delRes.modifiedCount === 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
90
server/src/modules/survey/controllers/session.controller.ts
Normal file
90
server/src/modules/survey/controllers/session.controller.ts
Normal file
@ -0,0 +1,90 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
HttpCode,
|
||||||
|
UseGuards,
|
||||||
|
SetMetadata,
|
||||||
|
Request,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { SessionService } from '../services/session.service';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { SessionGuard } from 'src/guards/session.guard';
|
||||||
|
|
||||||
|
@ApiTags('survey')
|
||||||
|
@Controller('/api/session')
|
||||||
|
export class SessionController {
|
||||||
|
constructor(
|
||||||
|
private readonly sessionService: SessionService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post('/create')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async create(
|
||||||
|
@Body()
|
||||||
|
reqBody: {
|
||||||
|
surveyId: string;
|
||||||
|
},
|
||||||
|
@Request()
|
||||||
|
req,
|
||||||
|
) {
|
||||||
|
const { value, error } = Joi.object({
|
||||||
|
surveyId: Joi.string().required(),
|
||||||
|
}).validate(reqBody);
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message);
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const surveyId = value.surveyId;
|
||||||
|
const session = await this.sessionService.create({
|
||||||
|
surveyId,
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
sessionId: session._id.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('/seize')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SessionGuard, SurveyGuard)
|
||||||
|
@SetMetadata('sessionId', 'body.sessionId')
|
||||||
|
@SetMetadata('surveyId', 'surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
async seize(
|
||||||
|
@Request()
|
||||||
|
req,
|
||||||
|
) {
|
||||||
|
const sessionInfo = req.sessionInfo;
|
||||||
|
|
||||||
|
await this.sessionService.updateSessionToEditing({
|
||||||
|
sessionId: sessionInfo._id.toString(),
|
||||||
|
surveyId: sessionInfo.surveyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -31,7 +31,8 @@ import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
|||||||
|
|
||||||
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||||
import { MemberType, WhitelistType } from 'src/interfaces/survey';
|
import { SessionService } from '../services/session.service';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/survey')
|
@Controller('/api/survey')
|
||||||
@ -43,6 +44,8 @@ export class SurveyController {
|
|||||||
private readonly contentSecurityService: ContentSecurityService,
|
private readonly contentSecurityService: ContentSecurityService,
|
||||||
private readonly surveyHistoryService: SurveyHistoryService,
|
private readonly surveyHistoryService: SurveyHistoryService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
|
private readonly sessionService: SessionService,
|
||||||
|
private readonly userService: UserService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/getBannerData')
|
@Get('/getBannerData')
|
||||||
@ -71,9 +74,7 @@ export class SurveyController {
|
|||||||
) {
|
) {
|
||||||
const { error, value } = CreateSurveyDto.validate(reqBody);
|
const { error, value } = CreateSurveyDto.validate(reqBody);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(`createSurvey_parameter error: ${error.message}`, {
|
this.logger.error(`createSurvey_parameter error: ${error.message}`);
|
||||||
req,
|
|
||||||
});
|
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,13 +130,41 @@ export class SurveyController {
|
|||||||
const { value, error } = Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
configData: Joi.any().required(),
|
configData: Joi.any().required(),
|
||||||
|
sessionId: Joi.string().required(),
|
||||||
}).validate(surveyInfo);
|
}).validate(surveyInfo);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const username = req.user.username;
|
const sessionId = value.sessionId;
|
||||||
const surveyId = value.surveyId;
|
const surveyId = value.surveyId;
|
||||||
|
const latestEditingOne = await this.sessionService.findLatestEditingOne({
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (latestEditingOne && latestEditingOne._id.toString() !== sessionId) {
|
||||||
|
const curSession = await this.sessionService.findOne(sessionId);
|
||||||
|
if (curSession.createdAt <= latestEditingOne.updatedAt) {
|
||||||
|
// 在当前用户打开之后,被其他页面保存过了
|
||||||
|
const isSameOperator =
|
||||||
|
latestEditingOne.userId === req.user._id.toString();
|
||||||
|
let preOperator;
|
||||||
|
if (!isSameOperator) {
|
||||||
|
preOperator = await this.userService.getUserById(
|
||||||
|
latestEditingOne.userId,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: EXCEPTION_CODE.SURVEY_SAVE_CONFLICT,
|
||||||
|
errmsg: isSameOperator
|
||||||
|
? '当前问卷已在其它页面开启编辑,刷新以获取最新内容'
|
||||||
|
: `当前问卷已由 ${preOperator.username} 编辑,刷新以获取最新内容`,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await this.sessionService.updateSessionToEditing({ sessionId, surveyId });
|
||||||
|
|
||||||
|
const username = req.user.username;
|
||||||
|
|
||||||
const configData = value.configData;
|
const configData = value.configData;
|
||||||
await this.surveyConfService.saveSurveyConf({
|
await this.surveyConfService.saveSurveyConf({
|
||||||
@ -165,10 +194,18 @@ export class SurveyController {
|
|||||||
async deleteSurvey(@Request() req) {
|
async deleteSurvey(@Request() req) {
|
||||||
const surveyMeta = req.surveyMeta;
|
const surveyMeta = req.surveyMeta;
|
||||||
|
|
||||||
await this.surveyMetaService.deleteSurveyMeta(surveyMeta);
|
const delMetaRes = await this.surveyMetaService.deleteSurveyMeta({
|
||||||
await this.responseSchemaService.deleteResponseSchema({
|
surveyId: surveyMeta._id.toString(),
|
||||||
surveyPath: surveyMeta.surveyPath,
|
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 {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -217,7 +254,7 @@ export class SurveyController {
|
|||||||
}).validate(queryInfo);
|
}).validate(queryInfo);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -234,16 +271,6 @@ export class SurveyController {
|
|||||||
surveyMeta.isCollaborated = false;
|
surveyMeta.isCollaborated = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 白名单相关字段的默认值
|
|
||||||
const baseConf = surveyConf.code?.baseConf;
|
|
||||||
if (baseConf) {
|
|
||||||
baseConf.passwordSwitch = baseConf.passwordSwitch ?? false;
|
|
||||||
baseConf.password = baseConf.password ?? '';
|
|
||||||
baseConf.whitelistType = baseConf.whitelistType ?? WhitelistType.ALL;
|
|
||||||
baseConf.whitelist = baseConf.whitelist ?? [];
|
|
||||||
baseConf.memberType = baseConf.memberType ?? MemberType.MOBILE;
|
|
||||||
}
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
@ -260,15 +287,13 @@ export class SurveyController {
|
|||||||
queryInfo: {
|
queryInfo: {
|
||||||
surveyPath: string;
|
surveyPath: string;
|
||||||
},
|
},
|
||||||
@Request()
|
|
||||||
req,
|
|
||||||
) {
|
) {
|
||||||
const { value, error } = Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
}).validate({ surveyId: queryInfo.surveyPath });
|
}).validate({ surveyId: queryInfo.surveyPath });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const surveyId = value.surveyId;
|
const surveyId = value.surveyId;
|
||||||
@ -301,12 +326,18 @@ export class SurveyController {
|
|||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
}).validate(surveyInfo);
|
}).validate(surveyInfo);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const surveyId = value.surveyId;
|
const surveyId = value.surveyId;
|
||||||
const surveyMeta = req.surveyMeta;
|
const surveyMeta = req.surveyMeta;
|
||||||
|
if (surveyMeta.isDeleted) {
|
||||||
|
throw new HttpException(
|
||||||
|
'问卷已删除,无法发布',
|
||||||
|
EXCEPTION_CODE.SURVEY_NOT_FOUND,
|
||||||
|
);
|
||||||
|
}
|
||||||
const surveyConf =
|
const surveyConf =
|
||||||
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
||||||
|
|
||||||
@ -332,7 +363,8 @@ export class SurveyController {
|
|||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
});
|
});
|
||||||
|
|
||||||
await this.surveyHistoryService.addHistory({
|
// 添加发布历史可以异步添加
|
||||||
|
this.surveyHistoryService.addHistory({
|
||||||
surveyId,
|
surveyId,
|
||||||
schema: surveyConf.code,
|
schema: surveyConf.code,
|
||||||
type: HISTORY_TYPE.PUBLISH_HIS,
|
type: HISTORY_TYPE.PUBLISH_HIS,
|
||||||
|
@ -5,7 +5,6 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
SetMetadata,
|
SetMetadata,
|
||||||
Request,
|
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
@ -18,9 +17,8 @@ import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
|||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/surveyHisotry')
|
@Controller('/api/surveyHistory')
|
||||||
export class SurveyHistoryController {
|
export class SurveyHistoryController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly surveyHistoryService: SurveyHistoryService,
|
private readonly surveyHistoryService: SurveyHistoryService,
|
||||||
@ -43,7 +41,6 @@ export class SurveyHistoryController {
|
|||||||
surveyId: string;
|
surveyId: string;
|
||||||
historyType: string;
|
historyType: string;
|
||||||
},
|
},
|
||||||
@Request() req,
|
|
||||||
) {
|
) {
|
||||||
const { value, error } = Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
@ -51,7 +48,7 @@ export class SurveyHistoryController {
|
|||||||
}).validate(queryInfo);
|
}).validate(queryInfo);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -51,16 +51,18 @@ export class SurveyMetaController {
|
|||||||
}).validate(reqBody, { allowUnknown: true });
|
}).validate(reqBody, { allowUnknown: true });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
this.logger.error(`updateMeta_parameter error: ${error.message}`);
|
||||||
req,
|
|
||||||
});
|
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const survey = req.surveyMeta;
|
const survey = req.surveyMeta;
|
||||||
survey.title = value.title;
|
survey.title = value.title;
|
||||||
survey.remark = value.remark;
|
survey.remark = value.remark;
|
||||||
|
|
||||||
await this.surveyMetaService.editSurveyMeta(survey);
|
await this.surveyMetaService.editSurveyMeta({
|
||||||
|
survey,
|
||||||
|
operator: req.user.username,
|
||||||
|
operatorId: req.user._id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -81,7 +83,7 @@ export class SurveyMetaController {
|
|||||||
) {
|
) {
|
||||||
const { value, error } = GetSurveyListDto.validate(queryInfo);
|
const { value, error } = GetSurveyListDto.validate(queryInfo);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
const { curPage, pageSize, workspaceId } = value;
|
const { curPage, pageSize, workspaceId } = value;
|
||||||
@ -91,14 +93,14 @@ export class SurveyMetaController {
|
|||||||
try {
|
try {
|
||||||
filter = getFilter(JSON.parse(decodeURIComponent(value.filter)));
|
filter = getFilter(JSON.parse(decodeURIComponent(value.filter)));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (value.order) {
|
if (value.order) {
|
||||||
try {
|
try {
|
||||||
order = order = getOrder(JSON.parse(decodeURIComponent(value.order)));
|
order = order = getOrder(JSON.parse(decodeURIComponent(value.order)));
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
this.logger.error(error.message, { req });
|
this.logger.error(error.message);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
const userId = req.user._id.toString();
|
const userId = req.user._id.toString();
|
||||||
@ -129,9 +131,10 @@ export class SurveyMetaController {
|
|||||||
if (!item.surveyType) {
|
if (!item.surveyType) {
|
||||||
item.surveyType = item.questionType || 'normal';
|
item.surveyType = item.questionType || 'normal';
|
||||||
}
|
}
|
||||||
item.createDate = moment(item.createDate).format(fmt);
|
item.createdAt = moment(item.createdAt).format(fmt);
|
||||||
item.curStatus.date = moment(item.curStatus.date).format(fmt);
|
item.curStatus.date = moment(item.curStatus.date).format(fmt);
|
||||||
item.subStatus.date = moment(item.subStatus.date).format(fmt);
|
item.subStatus.date = moment(item.subStatus.date).format(fmt);
|
||||||
|
item.updatedAt = moment(item.updatedAt).format(fmt);
|
||||||
const surveyId = item._id.toString();
|
const surveyId = item._id.toString();
|
||||||
if (cooperSurveyIdMap[surveyId]) {
|
if (cooperSurveyIdMap[surveyId]) {
|
||||||
item.isCollaborated = true;
|
item.isCollaborated = true;
|
||||||
|
@ -12,10 +12,10 @@ export class CreateSurveyDto {
|
|||||||
surveyType: string;
|
surveyType: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '创建方法', required: false })
|
@ApiProperty({ description: '创建方法', required: false })
|
||||||
createMethod: string;
|
createMethod?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '创建来源', required: false })
|
@ApiProperty({ description: '创建来源', required: false })
|
||||||
createFrom: string;
|
createFrom?: string;
|
||||||
|
|
||||||
@ApiProperty({ description: '问卷创建在哪个空间下', required: false })
|
@ApiProperty({ description: '问卷创建在哪个空间下', required: false })
|
||||||
workspaceId?: string;
|
workspaceId?: string;
|
||||||
|
51
server/src/modules/survey/dto/downloadTask.dto.ts
Normal file
51
server/src/modules/survey/dto/downloadTask.dto.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
|
||||||
|
export class CreateDownloadDto {
|
||||||
|
@ApiProperty({ description: '问卷id', required: true })
|
||||||
|
surveyId: string;
|
||||||
|
@ApiProperty({ description: '是否脱敏', required: false })
|
||||||
|
isMasked: boolean;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
surveyId: Joi.string().required(),
|
||||||
|
isMasked: Joi.boolean().allow(null).default(false),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export class GetDownloadTaskListDto {
|
||||||
|
@ApiProperty({ description: '当前页', required: false })
|
||||||
|
pageIndex: number;
|
||||||
|
@ApiProperty({ description: '一页大小', required: false })
|
||||||
|
pageSize: number;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
pageIndex: Joi.number().default(1),
|
||||||
|
pageSize: Joi.number().default(20),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class GetDownloadTaskDto {
|
||||||
|
@ApiProperty({ description: '任务id', required: true })
|
||||||
|
taskId: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
taskId: Joi.string().required(),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class DeleteDownloadTaskDto {
|
||||||
|
@ApiProperty({ description: '任务id', required: true })
|
||||||
|
taskId: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
taskId: Joi.string().required(),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
@ -22,12 +22,17 @@ export class CollaboratorService {
|
|||||||
return this.collaboratorRepository.save(collaborator);
|
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(
|
const res = await this.collaboratorRepository.insertMany(
|
||||||
collaboratorList.map((item) => {
|
collaboratorList.map((item) => {
|
||||||
return {
|
return {
|
||||||
...item,
|
...item,
|
||||||
surveyId,
|
surveyId,
|
||||||
|
createdAt: now,
|
||||||
|
updatedAt: now,
|
||||||
|
creator,
|
||||||
|
creatorId,
|
||||||
};
|
};
|
||||||
}),
|
}),
|
||||||
);
|
);
|
||||||
@ -60,7 +65,13 @@ export class CollaboratorService {
|
|||||||
return info;
|
return info;
|
||||||
}
|
}
|
||||||
|
|
||||||
async changeUserPermission({ userId, surveyId, permission }) {
|
async changeUserPermission({
|
||||||
|
userId,
|
||||||
|
surveyId,
|
||||||
|
permission,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
|
}) {
|
||||||
const updateRes = await this.collaboratorRepository.updateOne(
|
const updateRes = await this.collaboratorRepository.updateOne(
|
||||||
{
|
{
|
||||||
surveyId,
|
surveyId,
|
||||||
@ -69,6 +80,9 @@ export class CollaboratorService {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
permission,
|
permission,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -134,7 +148,7 @@ export class CollaboratorService {
|
|||||||
return delRes;
|
return delRes;
|
||||||
}
|
}
|
||||||
|
|
||||||
updateById({ collaboratorId, permissions }) {
|
updateById({ collaboratorId, permissions, operator, operatorId }) {
|
||||||
return this.collaboratorRepository.updateOne(
|
return this.collaboratorRepository.updateOne(
|
||||||
{
|
{
|
||||||
_id: new ObjectId(collaboratorId),
|
_id: new ObjectId(collaboratorId),
|
||||||
@ -142,6 +156,9 @@ export class CollaboratorService {
|
|||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
permissions,
|
permissions,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { keyBy } from 'lodash';
|
import { keyBy } from 'lodash';
|
||||||
@ -35,8 +34,8 @@ export class DataStatisticService {
|
|||||||
const dataListMap = keyBy(dataList, 'field');
|
const dataListMap = keyBy(dataList, 'field');
|
||||||
const where = {
|
const where = {
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
const [surveyResponseList, total] =
|
const [surveyResponseList, total] =
|
||||||
@ -45,7 +44,7 @@ export class DataStatisticService {
|
|||||||
take: pageSize,
|
take: pageSize,
|
||||||
skip: (pageNum - 1) * pageSize,
|
skip: (pageNum - 1) * pageSize,
|
||||||
order: {
|
order: {
|
||||||
createDate: -1,
|
createdAt: -1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -91,10 +90,10 @@ export class DataStatisticService {
|
|||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
...data,
|
...data,
|
||||||
diffTime: (submitedData.diffTime / 1000).toFixed(2),
|
diffTime: submitedData.diffTime
|
||||||
createDate: moment(submitedData.createDate).format(
|
? (submitedData.diffTime / 1000).toFixed(2)
|
||||||
'YYYY-MM-DD HH:mm:ss',
|
: '0',
|
||||||
),
|
createdAt: moment(submitedData.createdAt).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
};
|
};
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -125,8 +124,8 @@ export class DataStatisticService {
|
|||||||
{
|
{
|
||||||
$match: {
|
$match: {
|
||||||
pageId: surveyId,
|
pageId: surveyId,
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
273
server/src/modules/survey/services/downloadTask.service.ts
Normal file
273
server/src/modules/survey/services/downloadTask.service.ts
Normal file
@ -0,0 +1,273 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
|
import { DownloadTask } from 'src/models/downloadTask.entity';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { ResponseSchemaService } from 'src/modules/surveyResponse/services/responseScheme.service';
|
||||||
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
|
import { DataStatisticService } from './dataStatistic.service';
|
||||||
|
import xlsx from 'node-xlsx';
|
||||||
|
import { load } from 'cheerio';
|
||||||
|
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 {
|
||||||
|
private static taskList: Array<any> = [];
|
||||||
|
private static isExecuting: boolean = false;
|
||||||
|
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(DownloadTask)
|
||||||
|
private readonly downloadTaskRepository: MongoRepository<DownloadTask>,
|
||||||
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
|
@InjectRepository(SurveyResponse)
|
||||||
|
private readonly surveyResponseRepository: MongoRepository<SurveyResponse>,
|
||||||
|
private readonly dataStatisticService: DataStatisticService,
|
||||||
|
private readonly fileService: FileService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createDownloadTask({
|
||||||
|
surveyId,
|
||||||
|
responseSchema,
|
||||||
|
creatorId,
|
||||||
|
creator,
|
||||||
|
params,
|
||||||
|
}: {
|
||||||
|
surveyId: string;
|
||||||
|
responseSchema: ResponseSchema;
|
||||||
|
creatorId: string;
|
||||||
|
creator: string;
|
||||||
|
params: any;
|
||||||
|
}) {
|
||||||
|
const filename = `${responseSchema.title}-${params.isMasked ? '脱敏' : '原'}回收数据-${moment().format('YYYYMMDDHHmmss')}.xlsx`;
|
||||||
|
const downloadTask = this.downloadTaskRepository.create({
|
||||||
|
surveyId,
|
||||||
|
surveyPath: responseSchema.surveyPath,
|
||||||
|
fileSize: '计算中',
|
||||||
|
creatorId,
|
||||||
|
creator,
|
||||||
|
params: {
|
||||||
|
...params,
|
||||||
|
title: responseSchema.title,
|
||||||
|
},
|
||||||
|
filename,
|
||||||
|
status: DOWNLOAD_TASK_STATUS.WAITING,
|
||||||
|
});
|
||||||
|
await this.downloadTaskRepository.save(downloadTask);
|
||||||
|
return downloadTask._id.toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDownloadTaskList({
|
||||||
|
creatorId,
|
||||||
|
pageIndex,
|
||||||
|
pageSize,
|
||||||
|
}: {
|
||||||
|
creatorId: string;
|
||||||
|
pageIndex: number;
|
||||||
|
pageSize: number;
|
||||||
|
}) {
|
||||||
|
const where = {
|
||||||
|
creatorId,
|
||||||
|
isDeleted: {
|
||||||
|
$ne: true,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const [surveyDownloadList, total] =
|
||||||
|
await this.downloadTaskRepository.findAndCount({
|
||||||
|
where,
|
||||||
|
take: pageSize,
|
||||||
|
skip: (pageIndex - 1) * pageSize,
|
||||||
|
order: {
|
||||||
|
createdAt: -1,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
total,
|
||||||
|
list: surveyDownloadList,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
async getDownloadTaskById({ taskId }) {
|
||||||
|
const res = await this.downloadTaskRepository.find({
|
||||||
|
where: {
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (Array.isArray(res) && res.length > 0) {
|
||||||
|
return res[0];
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteDownloadTask({
|
||||||
|
taskId,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
|
}: {
|
||||||
|
taskId: string;
|
||||||
|
operator: string;
|
||||||
|
operatorId: string;
|
||||||
|
}) {
|
||||||
|
return this.downloadTaskRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
isDeleted: true,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
|
deletedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
processDownloadTask({ taskId }) {
|
||||||
|
DownloadTaskService.taskList.push(taskId);
|
||||||
|
if (!DownloadTaskService.isExecuting) {
|
||||||
|
this.executeTask();
|
||||||
|
DownloadTaskService.isExecuting = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async executeTask() {
|
||||||
|
try {
|
||||||
|
while (DownloadTaskService.taskList.length > 0) {
|
||||||
|
const taskId = DownloadTaskService.taskList.shift();
|
||||||
|
this.logger.info(`handle taskId: ${taskId}`);
|
||||||
|
const taskInfo = await this.getDownloadTaskById({ taskId });
|
||||||
|
if (!taskInfo || taskInfo.isDeleted) {
|
||||||
|
// 不存在或者已删除的,不处理
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
await this.handleDownloadTask({ taskInfo });
|
||||||
|
}
|
||||||
|
} finally {
|
||||||
|
DownloadTaskService.isExecuting = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private async handleDownloadTask({ taskInfo }) {
|
||||||
|
try {
|
||||||
|
// 更新任务状态为计算中
|
||||||
|
const updateRes = await this.downloadTaskRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: taskInfo._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
status: DOWNLOAD_TASK_STATUS.COMPUTING,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
this.logger.info(JSON.stringify(updateRes));
|
||||||
|
|
||||||
|
// 开始计算任务
|
||||||
|
const surveyId = taskInfo.surveyId;
|
||||||
|
const responseSchema =
|
||||||
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
|
const where = {
|
||||||
|
pageId: surveyId,
|
||||||
|
};
|
||||||
|
const total = await this.surveyResponseRepository.count(where);
|
||||||
|
const pageSize = 200;
|
||||||
|
const pageTotal = Math.ceil(total / pageSize);
|
||||||
|
const xlsxHead = [];
|
||||||
|
const xlsxBody = [];
|
||||||
|
for (let pageIndex = 1; pageIndex <= pageTotal; pageIndex++) {
|
||||||
|
const { listHead, listBody } =
|
||||||
|
await this.dataStatisticService.getDataTable({
|
||||||
|
surveyId,
|
||||||
|
pageNum: pageIndex,
|
||||||
|
pageSize,
|
||||||
|
responseSchema,
|
||||||
|
});
|
||||||
|
if (xlsxHead.length === 0) {
|
||||||
|
for (const item of listHead) {
|
||||||
|
const $ = load(item.title);
|
||||||
|
const text = $.text();
|
||||||
|
xlsxHead.push(text);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (const bodyItem of listBody) {
|
||||||
|
const bodyData = [];
|
||||||
|
for (const headItem of listHead) {
|
||||||
|
const field = headItem.field;
|
||||||
|
const val = get(bodyItem, field, '');
|
||||||
|
if (typeof val === 'string') {
|
||||||
|
const $ = load(val);
|
||||||
|
const text = $.text();
|
||||||
|
bodyData.push(text);
|
||||||
|
} else {
|
||||||
|
bodyData.push(val);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
xlsxBody.push(bodyData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const xlsxData = [xlsxHead, ...xlsxBody];
|
||||||
|
const buffer = await xlsx.build([
|
||||||
|
{ name: 'sheet1', data: xlsxData, options: {} },
|
||||||
|
]);
|
||||||
|
|
||||||
|
const file: Express.Multer.File = {
|
||||||
|
fieldname: 'file',
|
||||||
|
originalname: taskInfo.filename,
|
||||||
|
encoding: '7bit',
|
||||||
|
mimetype: 'application/octet-stream',
|
||||||
|
filename: taskInfo.filename,
|
||||||
|
size: buffer.length,
|
||||||
|
buffer: buffer,
|
||||||
|
stream: null,
|
||||||
|
destination: null,
|
||||||
|
path: '',
|
||||||
|
};
|
||||||
|
const { url, key } = await this.fileService.upload({
|
||||||
|
configKey: 'SERVER_LOCAL_CONFIG',
|
||||||
|
file,
|
||||||
|
pathPrefix: 'exportfile',
|
||||||
|
filename: taskInfo.filename,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 更新计算结果
|
||||||
|
const updateFinishRes = await this.downloadTaskRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: taskInfo._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
status: DOWNLOAD_TASK_STATUS.SUCCEED,
|
||||||
|
url,
|
||||||
|
fileKey: key,
|
||||||
|
fileSize: buffer.length,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.logger.info(JSON.stringify(updateFinishRes));
|
||||||
|
} catch (error) {
|
||||||
|
await this.downloadTaskRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: taskInfo._id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
status: DOWNLOAD_TASK_STATUS.FAILED,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
this.logger.error(
|
||||||
|
`导出文件失败 taskId: ${taskInfo._id.toString()}, surveyId: ${taskInfo.surveyId}, message: ${error.message}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
68
server/src/modules/survey/services/session.service.ts
Normal file
68
server/src/modules/survey/services/session.service.ts
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { Session } from 'src/models/session.entity';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { SESSION_STATUS } from 'src/enums/surveySessionStatus';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SessionService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Session)
|
||||||
|
private readonly sessionRepository: MongoRepository<Session>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
create({ surveyId, userId }) {
|
||||||
|
const session = this.sessionRepository.create({
|
||||||
|
surveyId,
|
||||||
|
userId,
|
||||||
|
status: SESSION_STATUS.DEACTIVATED,
|
||||||
|
});
|
||||||
|
return this.sessionRepository.save(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
findOne(sessionId) {
|
||||||
|
return this.sessionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
_id: new ObjectId(sessionId),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
findLatestEditingOne({ surveyId }) {
|
||||||
|
return this.sessionRepository.findOne({
|
||||||
|
where: {
|
||||||
|
surveyId,
|
||||||
|
status: SESSION_STATUS.ACTIVATED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
updateSessionToEditing({ sessionId, surveyId }) {
|
||||||
|
return Promise.all([
|
||||||
|
this.sessionRepository.update(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(sessionId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
status: SESSION_STATUS.ACTIVATED,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
),
|
||||||
|
this.sessionRepository.updateMany(
|
||||||
|
{
|
||||||
|
surveyId,
|
||||||
|
_id: {
|
||||||
|
$ne: new ObjectId(sessionId),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
status: SESSION_STATUS.DEACTIVATED,
|
||||||
|
updatedAt: new Date(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
),
|
||||||
|
]);
|
||||||
|
}
|
||||||
|
}
|
@ -45,9 +45,9 @@ export class SurveyHistoryService {
|
|||||||
},
|
},
|
||||||
take: 100,
|
take: 100,
|
||||||
order: {
|
order: {
|
||||||
createDate: -1,
|
createdAt: -1,
|
||||||
},
|
},
|
||||||
select: ['createDate', 'operator', 'type', '_id'],
|
select: ['createdAt', 'operator', 'type', '_id'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,14 +6,14 @@ import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
|||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SurveyMetaService {
|
export class SurveyMetaService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SurveyMeta)
|
@InjectRepository(SurveyMeta)
|
||||||
private readonly surveyRepository: MongoRepository<SurveyMeta>,
|
private readonly surveyRepository: MongoRepository<SurveyMeta>,
|
||||||
private readonly pluginManager: XiaojuSurveyPluginManager,
|
private readonly pluginManager: PluginManager,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async getNewSurveyPath(): Promise<string> {
|
async getNewSurveyPath(): Promise<string> {
|
||||||
@ -65,6 +65,7 @@ export class SurveyMetaService {
|
|||||||
surveyType: surveyType,
|
surveyType: surveyType,
|
||||||
surveyPath,
|
surveyPath,
|
||||||
creator: username,
|
creator: username,
|
||||||
|
creatorId: userId,
|
||||||
owner: username,
|
owner: username,
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
createMethod,
|
createMethod,
|
||||||
@ -76,11 +77,7 @@ export class SurveyMetaService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async pausingSurveyMeta(survey: SurveyMeta) {
|
async pausingSurveyMeta(survey: SurveyMeta) {
|
||||||
if (
|
if (survey?.curStatus?.status === RECORD_STATUS.NEW) {
|
||||||
survey.curStatus.status !== RECORD_STATUS.PUBLISHED ||
|
|
||||||
(survey?.subStatus?.status &&
|
|
||||||
survey?.subStatus?.status != RECORD_SUB_STATUS.EDITING)
|
|
||||||
) {
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'问卷不能暂停',
|
'问卷不能暂停',
|
||||||
EXCEPTION_CODE.SURVEY_STATUS_TRANSFORM_ERROR,
|
EXCEPTION_CODE.SURVEY_STATUS_TRANSFORM_ERROR,
|
||||||
@ -91,7 +88,6 @@ export class SurveyMetaService {
|
|||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
survey.subStatus = subCurStatus;
|
survey.subStatus = subCurStatus;
|
||||||
survey.curStatus.status = RECORD_STATUS.PUBLISHED;
|
|
||||||
if (Array.isArray(survey.statusList)) {
|
if (Array.isArray(survey.statusList)) {
|
||||||
survey.statusList.push(subCurStatus);
|
survey.statusList.push(subCurStatus);
|
||||||
} else {
|
} else {
|
||||||
@ -100,39 +96,43 @@ export class SurveyMetaService {
|
|||||||
return this.surveyRepository.save(survey);
|
return this.surveyRepository.save(survey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async editSurveyMeta(survey: SurveyMeta) {
|
async editSurveyMeta({
|
||||||
if (
|
survey,
|
||||||
survey.curStatus.status !== RECORD_STATUS.NEW &&
|
operator,
|
||||||
survey.subStatus.status !== RECORD_SUB_STATUS.EDITING
|
operatorId,
|
||||||
) {
|
}: {
|
||||||
|
survey: SurveyMeta;
|
||||||
|
operator: string;
|
||||||
|
operatorId: string;
|
||||||
|
}) {
|
||||||
|
if (survey?.curStatus?.status !== RECORD_STATUS.EDITING) {
|
||||||
const newStatus = {
|
const newStatus = {
|
||||||
status: RECORD_SUB_STATUS.EDITING,
|
status: RECORD_STATUS.EDITING,
|
||||||
date: Date.now(),
|
date: Date.now(),
|
||||||
};
|
};
|
||||||
survey.subStatus = newStatus;
|
survey.curStatus = newStatus;
|
||||||
survey.statusList.push(newStatus);
|
survey.statusList.push(newStatus);
|
||||||
}
|
}
|
||||||
|
survey.updatedAt = new Date();
|
||||||
|
survey.operator = operator;
|
||||||
|
survey.operatorId = operatorId;
|
||||||
return this.surveyRepository.save(survey);
|
return this.surveyRepository.save(survey);
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteSurveyMeta(survey: SurveyMeta) {
|
async deleteSurveyMeta({ surveyId, operator, operatorId }) {
|
||||||
if (survey.subStatus.status === RECORD_SUB_STATUS.REMOVED) {
|
return this.surveyRepository.updateOne(
|
||||||
throw new HttpException(
|
{
|
||||||
'问卷已删除,不能重复删除',
|
_id: new ObjectId(surveyId),
|
||||||
EXCEPTION_CODE.SURVEY_STATUS_TRANSFORM_ERROR,
|
},
|
||||||
);
|
{
|
||||||
}
|
$set: {
|
||||||
const newStatusInfo = {
|
isDeleted: true,
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
operator,
|
||||||
date: Date.now(),
|
operatorId,
|
||||||
};
|
deletedAt: new Date(),
|
||||||
survey.subStatus = newStatusInfo;
|
},
|
||||||
if (Array.isArray(survey.statusList)) {
|
},
|
||||||
survey.statusList.push(newStatusInfo);
|
);
|
||||||
} else {
|
|
||||||
survey.statusList = [newStatusInfo];
|
|
||||||
}
|
|
||||||
return this.surveyRepository.save(survey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSurveyMetaList(condition: {
|
async getSurveyMetaList(condition: {
|
||||||
@ -150,10 +150,9 @@ export class SurveyMetaService {
|
|||||||
const skip = (pageNum - 1) * pageSize;
|
const skip = (pageNum - 1) * pageSize;
|
||||||
try {
|
try {
|
||||||
const query: Record<string, any> = Object.assign(
|
const query: Record<string, any> = Object.assign(
|
||||||
{},
|
|
||||||
{
|
{
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
condition.filter,
|
condition.filter,
|
||||||
@ -188,25 +187,13 @@ export class SurveyMetaService {
|
|||||||
condition.order && Object.keys(condition.order).length > 0
|
condition.order && Object.keys(condition.order).length > 0
|
||||||
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
||||||
: ({
|
: ({
|
||||||
createDate: -1,
|
createdAt: -1,
|
||||||
} as FindOptionsOrder<SurveyMeta>);
|
} as FindOptionsOrder<SurveyMeta>);
|
||||||
const [data, count] = await this.surveyRepository.findAndCount({
|
const [data, count] = await this.surveyRepository.findAndCount({
|
||||||
where: query,
|
where: query,
|
||||||
skip,
|
skip,
|
||||||
take: pageSize,
|
take: pageSize,
|
||||||
order,
|
order,
|
||||||
select: [
|
|
||||||
'_id',
|
|
||||||
'title',
|
|
||||||
'remark',
|
|
||||||
'surveyType',
|
|
||||||
'curStatus',
|
|
||||||
'subStatus',
|
|
||||||
'createDate',
|
|
||||||
'owner',
|
|
||||||
'ownerId',
|
|
||||||
'workspaceId',
|
|
||||||
],
|
|
||||||
});
|
});
|
||||||
return { data, count };
|
return { data, count };
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
@ -235,8 +222,8 @@ export class SurveyMetaService {
|
|||||||
async countSurveyMetaByWorkspaceId({ workspaceId }) {
|
async countSurveyMetaByWorkspaceId({ workspaceId }) {
|
||||||
const total = await this.surveyRepository.count({
|
const total = await this.surveyRepository.count({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return total;
|
return total;
|
||||||
|
@ -7,6 +7,7 @@ import { LoggerProvider } from 'src/logger/logger.provider';
|
|||||||
import { SurveyResponseModule } from '../surveyResponse/surveyResponse.module';
|
import { SurveyResponseModule } from '../surveyResponse/surveyResponse.module';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||||
|
import { FileModule } from '../file/file.module';
|
||||||
|
|
||||||
import { DataStatisticController } from './controllers/dataStatistic.controller';
|
import { DataStatisticController } from './controllers/dataStatistic.controller';
|
||||||
import { SurveyController } from './controllers/survey.controller';
|
import { SurveyController } from './controllers/survey.controller';
|
||||||
@ -14,6 +15,8 @@ import { SurveyHistoryController } from './controllers/surveyHistory.controller'
|
|||||||
import { SurveyMetaController } from './controllers/surveyMeta.controller';
|
import { SurveyMetaController } from './controllers/surveyMeta.controller';
|
||||||
import { SurveyUIController } from './controllers/surveyUI.controller';
|
import { SurveyUIController } from './controllers/surveyUI.controller';
|
||||||
import { CollaboratorController } from './controllers/collaborator.controller';
|
import { CollaboratorController } from './controllers/collaborator.controller';
|
||||||
|
import { DownloadTaskController } from './controllers/downloadTask.controller';
|
||||||
|
import { SessionController } from './controllers/session.controller';
|
||||||
|
|
||||||
import { SurveyConf } from 'src/models/surveyConf.entity';
|
import { SurveyConf } from 'src/models/surveyConf.entity';
|
||||||
import { SurveyHistory } from 'src/models/surveyHistory.entity';
|
import { SurveyHistory } from 'src/models/surveyHistory.entity';
|
||||||
@ -21,14 +24,21 @@ import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
|||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { Word } from 'src/models/word.entity';
|
import { Word } from 'src/models/word.entity';
|
||||||
import { Collaborator } from 'src/models/collaborator.entity';
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { DownloadTask } from 'src/models/downloadTask.entity';
|
||||||
|
|
||||||
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { DataStatisticService } from './services/dataStatistic.service';
|
import { DataStatisticService } from './services/dataStatistic.service';
|
||||||
import { SurveyConfService } from './services/surveyConf.service';
|
import { SurveyConfService } from './services/surveyConf.service';
|
||||||
import { SurveyHistoryService } from './services/surveyHistory.service';
|
import { SurveyHistoryService } from './services/surveyHistory.service';
|
||||||
import { SurveyMetaService } from './services/surveyMeta.service';
|
import { SurveyMetaService } from './services/surveyMeta.service';
|
||||||
import { ContentSecurityService } from './services/contentSecurity.service';
|
import { ContentSecurityService } from './services/contentSecurity.service';
|
||||||
import { CollaboratorService } from './services/collaborator.service';
|
import { CollaboratorService } from './services/collaborator.service';
|
||||||
|
import { Counter } from 'src/models/counter.entity';
|
||||||
|
import { CounterService } from '../surveyResponse/services/counter.service';
|
||||||
|
import { FileService } from '../file/services/file.service';
|
||||||
|
import { DownloadTaskService } from './services/downloadTask.service';
|
||||||
|
import { SessionService } from './services/session.service';
|
||||||
|
import { Session } from 'src/models/session.entity';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -39,11 +49,15 @@ import { CollaboratorService } from './services/collaborator.service';
|
|||||||
SurveyResponse,
|
SurveyResponse,
|
||||||
Word,
|
Word,
|
||||||
Collaborator,
|
Collaborator,
|
||||||
|
Counter,
|
||||||
|
DownloadTask,
|
||||||
|
Session,
|
||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
SurveyResponseModule,
|
SurveyResponseModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
|
FileModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
DataStatisticController,
|
DataStatisticController,
|
||||||
@ -52,6 +66,8 @@ import { CollaboratorService } from './services/collaborator.service';
|
|||||||
SurveyMetaController,
|
SurveyMetaController,
|
||||||
SurveyUIController,
|
SurveyUIController,
|
||||||
CollaboratorController,
|
CollaboratorController,
|
||||||
|
DownloadTaskController,
|
||||||
|
SessionController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DataStatisticService,
|
DataStatisticService,
|
||||||
@ -62,6 +78,10 @@ import { CollaboratorService } from './services/collaborator.service';
|
|||||||
ContentSecurityService,
|
ContentSecurityService,
|
||||||
CollaboratorService,
|
CollaboratorService,
|
||||||
LoggerProvider,
|
LoggerProvider,
|
||||||
|
CounterService,
|
||||||
|
DownloadTaskService,
|
||||||
|
FileService,
|
||||||
|
SessionService,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SurveyModule {}
|
export class SurveyModule {}
|
||||||
|
@ -43,7 +43,6 @@
|
|||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"text": "选项1",
|
"text": "选项1",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -52,7 +51,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "选项2",
|
"text": "选项2",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
|
@ -47,7 +47,6 @@
|
|||||||
{
|
{
|
||||||
"text": "课程1",
|
"text": "课程1",
|
||||||
"hash": "115019",
|
"hash": "115019",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -56,7 +55,6 @@
|
|||||||
{
|
{
|
||||||
"text": "课程2",
|
"text": "课程2",
|
||||||
"hash": "115020",
|
"hash": "115020",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -65,7 +63,6 @@
|
|||||||
{
|
{
|
||||||
"text": "课程3",
|
"text": "课程3",
|
||||||
"hash": "115021",
|
"hash": "115021",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -74,7 +71,6 @@
|
|||||||
{
|
{
|
||||||
"text": "课程4",
|
"text": "课程4",
|
||||||
"hash": "115022",
|
"hash": "115022",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
|
@ -41,12 +41,11 @@
|
|||||||
"innerType": "radio",
|
"innerType": "radio",
|
||||||
"field": "data606",
|
"field": "data606",
|
||||||
"title": "标题2",
|
"title": "标题2",
|
||||||
"minNum": "",
|
"minNum": 0,
|
||||||
"maxNum": "",
|
"maxNum": 0,
|
||||||
"options": [
|
"options": [
|
||||||
{
|
{
|
||||||
"text": "选项1",
|
"text": "选项1",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
@ -55,7 +54,6 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"text": "选项2",
|
"text": "选项2",
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
"others": false,
|
||||||
"mustOthers": false,
|
"mustOthers": false,
|
||||||
"othersKey": "",
|
"othersKey": "",
|
||||||
|
@ -2,10 +2,12 @@
|
|||||||
"bannerConf": {
|
"bannerConf": {
|
||||||
"titleConfig": {
|
"titleConfig": {
|
||||||
"mainTitle": "<h3 style=\"text-align: center\">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style=\"color: rgb(204, 0, 0)\">期待您的参与!</span></p>",
|
"mainTitle": "<h3 style=\"text-align: center\">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style=\"color: rgb(204, 0, 0)\">期待您的参与!</span></p>",
|
||||||
"subTitle": ""
|
"subTitle": "<p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style=\"color: rgb(204, 0, 0)\">期待您的参与!</span></p>"
|
||||||
},
|
},
|
||||||
"bannerConfig": {
|
"bannerConfig": {
|
||||||
"bgImage": "/imgs/skin/17e06b7604a007e1d3e1453b9ddadc3c.webp",
|
"bgImage": "/imgs/skin/17e06b7604a007e1d3e1453b9ddadc3c.webp",
|
||||||
|
"bgImageAllowJump": false,
|
||||||
|
"bgImageJumpLink": "",
|
||||||
"videoLink": "",
|
"videoLink": "",
|
||||||
"postImg": ""
|
"postImg": ""
|
||||||
}
|
}
|
||||||
@ -22,25 +24,35 @@
|
|||||||
"msg_9002": "请勿多次提交!",
|
"msg_9002": "请勿多次提交!",
|
||||||
"msg_9003": "您来晚了,已经满额!",
|
"msg_9003": "您来晚了,已经满额!",
|
||||||
"msg_9004": "提交失败!"
|
"msg_9004": "提交失败!"
|
||||||
}
|
},
|
||||||
|
"link": ""
|
||||||
},
|
},
|
||||||
"bottomConf": {
|
"bottomConf": {
|
||||||
"logoImage": "/imgs/Logo.webp",
|
"logoImage": "/imgs/Logo.webp",
|
||||||
"logoImageWidth": "60%"
|
"logoImageWidth": "60%"
|
||||||
},
|
},
|
||||||
"baseConf": {
|
"baseConf": {
|
||||||
"begTime": "2024-01-01 00:00:00",
|
"beginTime": "2024-01-01 00:00:00",
|
||||||
"endTime": "2034-01-01 00:00:00",
|
"endTime": "2034-01-01 00:00:00",
|
||||||
"tLimit": 0,
|
"tLimit": 0,
|
||||||
"language": "chinese",
|
"language": "chinese",
|
||||||
"answerBegTime": "00:00:00",
|
"answerBegTime": "00:00:00",
|
||||||
"answerEndTime": "23:59:59"
|
"answerEndTime": "23:59:59",
|
||||||
|
"passwordSwitch": false,
|
||||||
|
"password": "",
|
||||||
|
"whitelistType": "ALL",
|
||||||
|
"whitelist": [],
|
||||||
|
"memberType": "MOBILE",
|
||||||
|
"fillAnswer": false,
|
||||||
|
"fillSubmitAnswer": false
|
||||||
},
|
},
|
||||||
"skinConf": {
|
"skinConf": {
|
||||||
"skinColor": "#4a4c5b",
|
"skinColor": "#4a4c5b",
|
||||||
"inputBgColor": "#ffffff",
|
"inputBgColor": "#ffffff",
|
||||||
"backgroundConf": {
|
"backgroundConf": {
|
||||||
"color": "#ffffff"
|
"color": "#b8dbff",
|
||||||
|
"type": "color",
|
||||||
|
"image": ""
|
||||||
},
|
},
|
||||||
"themeConf": {
|
"themeConf": {
|
||||||
"color": "#ffa600"
|
"color": "#ffa600"
|
||||||
@ -51,6 +63,7 @@
|
|||||||
},
|
},
|
||||||
"pageConf": [],
|
"pageConf": [],
|
||||||
"logicConf": {
|
"logicConf": {
|
||||||
"showLogicConf": []
|
"showLogicConf": [],
|
||||||
|
"jumpLogicConf": []
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -22,7 +22,7 @@ export async function getSchemaBySurveyType(surveyType: string) {
|
|||||||
}
|
}
|
||||||
const code = Object.assign({}, templateBase, codeData);
|
const code = Object.assign({}, templateBase, codeData);
|
||||||
const nowMoment = moment();
|
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
|
code.baseConf.endTime = nowMoment
|
||||||
.add(10, 'years')
|
.add(10, 'years')
|
||||||
.format('YYYY-MM-DD HH:mm:ss');
|
.format('YYYY-MM-DD HH:mm:ss');
|
||||||
@ -63,7 +63,7 @@ export function getListHeadByDataList(dataList) {
|
|||||||
type: QUESTION_TYPE.TEXT,
|
type: QUESTION_TYPE.TEXT,
|
||||||
});
|
});
|
||||||
listHead.push({
|
listHead.push({
|
||||||
field: 'createDate',
|
field: 'createdAt',
|
||||||
title: '提交时间',
|
title: '提交时间',
|
||||||
type: QUESTION_TYPE.TEXT,
|
type: QUESTION_TYPE.TEXT,
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,6 @@ import { MongoRepository } from 'typeorm';
|
|||||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
|
||||||
@ -88,9 +87,6 @@ describe('ClientEncryptService', () => {
|
|||||||
expect(repository.findOne).toHaveBeenCalledWith({
|
expect(repository.findOne).toHaveBeenCalledWith({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
expect(result).toEqual(encryptInfo);
|
expect(result).toEqual(encryptInfo);
|
||||||
|
@ -25,8 +25,7 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
code: {
|
code: {
|
||||||
bannerConf: {
|
bannerConf: {
|
||||||
titleConfig: {
|
titleConfig: {
|
||||||
mainTitle:
|
mainTitle: '<h3 style="text-align: center">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>',
|
||||||
'<h3 style="text-align: center">欢迎填写问卷</h3><p>为了给您提供更好的服务,希望您能抽出几分钟时间,将您的感受和建议告诉我们,<span style="color: rgb(204, 0, 0)">期待您的参与!</span></p>',
|
|
||||||
subTitle: '',
|
subTitle: '',
|
||||||
},
|
},
|
||||||
bannerConfig: {
|
bannerConfig: {
|
||||||
@ -36,7 +35,7 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
baseConf: {
|
baseConf: {
|
||||||
begTime: '2024-03-14 14:54:41',
|
beginTime: '2024-03-14 14:54:41',
|
||||||
endTime: '2034-03-14 14:54:41',
|
endTime: '2034-03-14 14:54:41',
|
||||||
language: 'chinese',
|
language: 'chinese',
|
||||||
tLimit: 10,
|
tLimit: 10,
|
||||||
@ -48,6 +47,17 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
logoImageWidth: '60%',
|
logoImageWidth: '60%',
|
||||||
},
|
},
|
||||||
skinConf: {
|
skinConf: {
|
||||||
|
backgroundConf: {
|
||||||
|
color: '#fff',
|
||||||
|
type: 'color',
|
||||||
|
image: '',
|
||||||
|
},
|
||||||
|
themeConf: {
|
||||||
|
color: '#ffa600',
|
||||||
|
},
|
||||||
|
contentConf: {
|
||||||
|
opacity: 100,
|
||||||
|
},
|
||||||
skinColor: '#4a4c5b',
|
skinColor: '#4a4c5b',
|
||||||
inputBgColor: '#ffffff',
|
inputBgColor: '#ffffff',
|
||||||
},
|
},
|
||||||
@ -240,4 +250,4 @@ export const mockResponseSchema: ResponseSchema = {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
pageId: '65f29f3192862d6a9067ad1c',
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
} as ResponseSchema;
|
} as unknown as ResponseSchema;
|
||||||
|
@ -13,7 +13,7 @@ import { ClientEncryptService } from '../services/clientEncrypt.service';
|
|||||||
import { MessagePushingTaskService } from 'src/modules/message/services/messagePushingTask.service';
|
import { MessagePushingTaskService } from 'src/modules/message/services/messagePushingTask.service';
|
||||||
|
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { PluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
@ -153,9 +153,7 @@ describe('SurveyResponseController', () => {
|
|||||||
clientEncryptService =
|
clientEncryptService =
|
||||||
module.get<ClientEncryptService>(ClientEncryptService);
|
module.get<ClientEncryptService>(ClientEncryptService);
|
||||||
|
|
||||||
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
const pluginManager = module.get<PluginManager>(PluginManager);
|
||||||
XiaojuSurveyPluginManager,
|
|
||||||
);
|
|
||||||
pluginManager.registerPlugin(
|
pluginManager.registerPlugin(
|
||||||
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
new ResponseSecurityPlugin('dataAesEncryptSecretKey'),
|
||||||
);
|
);
|
||||||
@ -220,7 +218,8 @@ describe('SurveyResponseController', () => {
|
|||||||
jest
|
jest
|
||||||
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
||||||
.mockResolvedValueOnce(undefined);
|
.mockResolvedValueOnce(undefined);
|
||||||
const result = await controller.createResponse(reqBody, {});
|
|
||||||
|
const result = await controller.createResponse(reqBody);
|
||||||
|
|
||||||
expect(result).toEqual({ code: 200, msg: '提交成功' });
|
expect(result).toEqual({ code: 200, msg: '提交成功' });
|
||||||
expect(
|
expect(
|
||||||
@ -267,7 +266,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValueOnce(null);
|
.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||||
SurveyNotFoundException,
|
SurveyNotFoundException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -276,7 +275,7 @@ describe('SurveyResponseController', () => {
|
|||||||
const reqBody = cloneDeep(mockSubmitData);
|
const reqBody = cloneDeep(mockSubmitData);
|
||||||
delete reqBody.sign;
|
delete reqBody.sign;
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -289,7 +288,7 @@ describe('SurveyResponseController', () => {
|
|||||||
const reqBody = cloneDeep(mockDecryptErrorBody);
|
const reqBody = cloneDeep(mockDecryptErrorBody);
|
||||||
reqBody.sign = 'mock sign';
|
reqBody.sign = 'mock sign';
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -305,7 +304,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValueOnce(mockResponseSchema);
|
.mockResolvedValueOnce(mockResponseSchema);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -317,7 +316,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValueOnce(mockResponseSchema);
|
.mockResolvedValueOnce(mockResponseSchema);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -346,7 +345,7 @@ describe('SurveyResponseController', () => {
|
|||||||
},
|
},
|
||||||
} as ResponseSchema);
|
} as ResponseSchema);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||||
new HttpException('白名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
new HttpException('白名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -44,16 +44,20 @@ export class ResponseSchemaController {
|
|||||||
await this.responseSchemaService.getResponseSchemaByPath(
|
await this.responseSchemaService.getResponseSchemaByPath(
|
||||||
queryInfo.surveyPath,
|
queryInfo.surveyPath,
|
||||||
);
|
);
|
||||||
if (
|
if (!responseSchema || responseSchema.isDeleted) {
|
||||||
!responseSchema ||
|
|
||||||
responseSchema.subStatus.status === RECORD_SUB_STATUS.REMOVED
|
|
||||||
) {
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'问卷已删除',
|
'问卷不存在或已删除',
|
||||||
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (responseSchema.subStatus.status === RECORD_SUB_STATUS.PAUSING) {
|
||||||
|
throw new HttpException(
|
||||||
|
'该问卷已暂停回收',
|
||||||
|
EXCEPTION_CODE.RESPONSE_PAUSING,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
// 去掉C端的敏感字段
|
// 去掉C端的敏感字段
|
||||||
if (responseSchema.code?.baseConf) {
|
if (responseSchema.code?.baseConf) {
|
||||||
responseSchema.code.baseConf.password = null;
|
responseSchema.code.baseConf.password = null;
|
||||||
@ -82,7 +86,7 @@ export class ResponseSchemaController {
|
|||||||
// 问卷信息
|
// 问卷信息
|
||||||
const schema =
|
const schema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
||||||
if (!schema || schema.subStatus.status === RECORD_SUB_STATUS.REMOVED) {
|
if (!schema || schema.isDeleted) {
|
||||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -97,14 +101,17 @@ export class ResponseSchemaController {
|
|||||||
// 密码校验
|
// 密码校验
|
||||||
if (passwordSwitch) {
|
if (passwordSwitch) {
|
||||||
if (settingPassword !== password) {
|
if (settingPassword !== password) {
|
||||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
throw new HttpException('密码验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 名单校验(手机号/邮箱)
|
// 名单校验(手机号/邮箱)
|
||||||
if (whitelistType === WhitelistType.CUSTOM) {
|
if (whitelistType === WhitelistType.CUSTOM) {
|
||||||
if (!whitelist.includes(whitelistValue)) {
|
if (!whitelist.includes(whitelistValue)) {
|
||||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
throw new HttpException(
|
||||||
|
'白名单验证失败',
|
||||||
|
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,7 +119,7 @@ export class ResponseSchemaController {
|
|||||||
if (whitelistType === WhitelistType.MEMBER) {
|
if (whitelistType === WhitelistType.MEMBER) {
|
||||||
const user = await this.userService.getUserByUsername(whitelistValue);
|
const user = await this.userService.getUserByUsername(whitelistValue);
|
||||||
if (!user) {
|
if (!user) {
|
||||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
throw new HttpException('名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Controller, Post, Body, HttpCode, Request } from '@nestjs/common';
|
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
import { checkSign } from 'src/utils/checkSign';
|
import { checkSign } from 'src/utils/checkSign';
|
||||||
@ -8,37 +8,48 @@ import { getPushingData } from 'src/utils/messagePushing';
|
|||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
import { RECORD_SUB_STATUS } from 'src/enums';
|
||||||
|
|
||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { CounterService } from '../services/counter.service';
|
|
||||||
import { SurveyResponseService } from '../services/surveyResponse.service';
|
import { SurveyResponseService } from '../services/surveyResponse.service';
|
||||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||||
import { MessagePushingTaskService } from '../../message/services/messagePushingTask.service';
|
import { MessagePushingTaskService } from '../../message/services/messagePushingTask.service';
|
||||||
|
// import { RedisService } from 'src/modules/redis/redis.service';
|
||||||
|
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import * as forge from 'node-forge';
|
import * as forge from 'node-forge';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { CounterService } from '../services/counter.service';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
import { WhitelistType } from 'src/interfaces/survey';
|
import { WhitelistType } from 'src/interfaces/survey';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { QUESTION_TYPE } from 'src/enums/question';
|
||||||
|
|
||||||
|
const optionQuestionType: Array<string> = [
|
||||||
|
QUESTION_TYPE.RADIO,
|
||||||
|
QUESTION_TYPE.CHECKBOX,
|
||||||
|
QUESTION_TYPE.BINARY_CHOICE,
|
||||||
|
QUESTION_TYPE.VOTE,
|
||||||
|
];
|
||||||
|
|
||||||
@ApiTags('surveyResponse')
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/surveyResponse')
|
@Controller('/api/surveyResponse')
|
||||||
export class SurveyResponseController {
|
export class SurveyResponseController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly counterService: CounterService,
|
|
||||||
private readonly surveyResponseService: SurveyResponseService,
|
private readonly surveyResponseService: SurveyResponseService,
|
||||||
private readonly clientEncryptService: ClientEncryptService,
|
private readonly clientEncryptService: ClientEncryptService,
|
||||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||||
|
private readonly counterService: CounterService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
|
// private readonly redisService: RedisService,
|
||||||
private readonly userService: UserService,
|
private readonly userService: UserService,
|
||||||
private readonly workspaceMemberService: WorkspaceMemberService,
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/createResponse')
|
@Post('/createResponse')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async createResponse(@Body() reqBody, @Request() req) {
|
async createResponse(@Body() reqBody) {
|
||||||
// 检查签名
|
// 检查签名
|
||||||
checkSign(reqBody);
|
checkSign(reqBody);
|
||||||
// 校验参数
|
// 校验参数
|
||||||
@ -54,9 +65,7 @@ export class SurveyResponseController {
|
|||||||
}).validate(reqBody, { allowUnknown: true });
|
}).validate(reqBody, { allowUnknown: true });
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
this.logger.error(`updateMeta_parameter error: ${error.message}`);
|
||||||
req,
|
|
||||||
});
|
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -74,10 +83,7 @@ export class SurveyResponseController {
|
|||||||
// 查询schema
|
// 查询schema
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
||||||
if (
|
if (!responseSchema || responseSchema.isDeleted) {
|
||||||
!responseSchema ||
|
|
||||||
responseSchema.subStatus.status === RECORD_SUB_STATUS.REMOVED
|
|
||||||
) {
|
|
||||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||||
}
|
}
|
||||||
if (responseSchema?.subStatus?.status === RECORD_SUB_STATUS.PAUSING) {
|
if (responseSchema?.subStatus?.status === RECORD_SUB_STATUS.PAUSING) {
|
||||||
@ -133,12 +139,12 @@ export class SurveyResponseController {
|
|||||||
|
|
||||||
const now = Date.now();
|
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;
|
const endTime = responseSchema?.code?.baseConf?.endTime || 0;
|
||||||
if (begTime && endTime) {
|
if (beginTime && endTime) {
|
||||||
const begTimeStamp = new Date(begTime).getTime();
|
const beginTimeStamp = new Date(beginTime).getTime();
|
||||||
const endTimeStamp = new Date(endTime).getTime();
|
const endTimeStamp = new Date(endTime).getTime();
|
||||||
if (now < begTimeStamp || now > endTimeStamp) {
|
if (now < beginTimeStamp || now > endTimeStamp) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
'不在答题有效期内',
|
'不在答题有效期内',
|
||||||
EXCEPTION_CODE.RESPONSE_CURRENT_TIME_NOT_ALLOW,
|
EXCEPTION_CODE.RESPONSE_CURRENT_TIME_NOT_ALLOW,
|
||||||
@ -215,6 +221,7 @@ export class SurveyResponseController {
|
|||||||
const optionTextAndId = dataList
|
const optionTextAndId = dataList
|
||||||
.filter((questionItem) => {
|
.filter((questionItem) => {
|
||||||
return (
|
return (
|
||||||
|
optionQuestionType.includes(questionItem.type) &&
|
||||||
Array.isArray(questionItem.options) &&
|
Array.isArray(questionItem.options) &&
|
||||||
questionItem.options.length > 0 &&
|
questionItem.options.length > 0 &&
|
||||||
decryptedData[questionItem.field]
|
decryptedData[questionItem.field]
|
||||||
@ -229,34 +236,55 @@ export class SurveyResponseController {
|
|||||||
return pre;
|
return pre;
|
||||||
}, {});
|
}, {});
|
||||||
|
|
||||||
// 对用户提交的数据进行遍历处理
|
const surveyId = responseSchema.pageId;
|
||||||
for (const field in decryptedData) {
|
// const lockKey = `locks:optionSelectedCount:${surveyId}`;
|
||||||
const val = decryptedData[field];
|
// const lock = await this.redisService.lockResource(lockKey, 1000);
|
||||||
const vals = Array.isArray(val) ? val : [val];
|
// this.logger.info(`lockKey: ${lockKey}`);
|
||||||
if (field in optionTextAndId) {
|
try {
|
||||||
// 记录选项的提交数量,用于投票题回显、或者拓展上限限制功能
|
const successParams = [];
|
||||||
const optionCountData: Record<string, any> =
|
for (const field in decryptedData) {
|
||||||
(await this.counterService.get({
|
const value = decryptedData[field];
|
||||||
surveyPath,
|
const values = Array.isArray(value) ? value : [value];
|
||||||
key: field,
|
if (field in optionTextAndId) {
|
||||||
type: 'option',
|
const optionCountData =
|
||||||
})) || { total: 0 };
|
(await this.counterService.get({
|
||||||
optionCountData.total++;
|
key: field,
|
||||||
for (const val of vals) {
|
surveyPath,
|
||||||
if (!optionCountData[val]) {
|
type: 'option',
|
||||||
optionCountData[val] = 1;
|
})) || {};
|
||||||
} else {
|
|
||||||
|
//遍历选项hash值
|
||||||
|
for (const val of values) {
|
||||||
|
if (!optionCountData[val]) {
|
||||||
|
optionCountData[val] = 0;
|
||||||
|
}
|
||||||
optionCountData[val]++;
|
optionCountData[val]++;
|
||||||
}
|
}
|
||||||
|
if (!optionCountData['total']) {
|
||||||
|
optionCountData['total'] = 1;
|
||||||
|
} else {
|
||||||
|
optionCountData['total']++;
|
||||||
|
}
|
||||||
|
successParams.push({
|
||||||
|
key: field,
|
||||||
|
surveyPath,
|
||||||
|
type: 'option',
|
||||||
|
data: optionCountData,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
this.counterService.set({
|
|
||||||
surveyPath,
|
|
||||||
key: field,
|
|
||||||
data: optionCountData,
|
|
||||||
type: 'option',
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
// 校验通过后统一更新
|
||||||
|
await Promise.all(
|
||||||
|
successParams.map((item) => this.counterService.set(item)),
|
||||||
|
);
|
||||||
|
} catch (error) {
|
||||||
|
this.logger.error(error.message);
|
||||||
|
throw error;
|
||||||
}
|
}
|
||||||
|
// finally {
|
||||||
|
// await this.redisService.unlockResource(lock);
|
||||||
|
// this.logger.info(`unlockResource: ${lockKey}`);
|
||||||
|
// }
|
||||||
|
|
||||||
// 入库
|
// 入库
|
||||||
const surveyResponse =
|
const surveyResponse =
|
||||||
@ -269,7 +297,6 @@ export class SurveyResponseController {
|
|||||||
optionTextAndId,
|
optionTextAndId,
|
||||||
});
|
});
|
||||||
|
|
||||||
const surveyId = responseSchema.pageId;
|
|
||||||
const sendData = getPushingData({
|
const sendData = getPushingData({
|
||||||
surveyResponse,
|
surveyResponse,
|
||||||
questionList: responseSchema?.code?.dataConf?.dataList || [],
|
questionList: responseSchema?.code?.dataConf?.dataList || [],
|
||||||
|
@ -4,7 +4,6 @@ import { MongoRepository } from 'typeorm';
|
|||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class ClientEncryptService {
|
export class ClientEncryptService {
|
||||||
@ -38,26 +37,13 @@ export class ClientEncryptService {
|
|||||||
return this.clientEncryptRepository.findOne({
|
return this.clientEncryptRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
deleteEncryptInfo(id: string) {
|
deleteEncryptInfo(id: string) {
|
||||||
return this.clientEncryptRepository.updateOne(
|
return this.clientEncryptRepository.deleteOne({
|
||||||
{
|
_id: new ObjectId(id),
|
||||||
_id: new ObjectId(id),
|
});
|
||||||
},
|
|
||||||
{
|
|
||||||
$set: {
|
|
||||||
subStatus: {
|
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
date: Date.now(),
|
|
||||||
},
|
|
||||||
},
|
|
||||||
},
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -33,6 +33,7 @@ export class CounterService {
|
|||||||
surveyPath,
|
surveyPath,
|
||||||
type,
|
type,
|
||||||
data,
|
data,
|
||||||
|
updatedAt: new Date(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -43,7 +43,6 @@ export class ResponseSchemaService {
|
|||||||
code,
|
code,
|
||||||
pageId,
|
pageId,
|
||||||
curStatus,
|
curStatus,
|
||||||
statusList: [curStatus],
|
|
||||||
subStatus,
|
subStatus,
|
||||||
});
|
});
|
||||||
return this.responseSchemaRepository.save(newClientSurvey);
|
return this.responseSchemaRepository.save(newClientSurvey);
|
||||||
@ -73,31 +72,21 @@ export class ResponseSchemaService {
|
|||||||
};
|
};
|
||||||
responseSchema.subStatus = subStatus;
|
responseSchema.subStatus = subStatus;
|
||||||
responseSchema.curStatus.status = RECORD_STATUS.PUBLISHED;
|
responseSchema.curStatus.status = RECORD_STATUS.PUBLISHED;
|
||||||
if (Array.isArray(responseSchema.statusList)) {
|
|
||||||
responseSchema.statusList.push(subStatus);
|
|
||||||
} else {
|
|
||||||
responseSchema.statusList = [subStatus];
|
|
||||||
}
|
|
||||||
return this.responseSchemaRepository.save(responseSchema);
|
return this.responseSchemaRepository.save(responseSchema);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async deleteResponseSchema({ surveyPath }) {
|
async deleteResponseSchema({ surveyPath }) {
|
||||||
const responseSchema = await this.responseSchemaRepository.findOne({
|
return this.responseSchemaRepository.updateOne(
|
||||||
where: { surveyPath },
|
{
|
||||||
});
|
surveyPath,
|
||||||
if (responseSchema) {
|
},
|
||||||
const newStatus = {
|
{
|
||||||
status: RECORD_STATUS.PUBLISHED,
|
$set: {
|
||||||
date: Date.now(),
|
isDeleted: true,
|
||||||
};
|
updatedAt: new Date(),
|
||||||
responseSchema.curStatus = newStatus;
|
},
|
||||||
if (Array.isArray(responseSchema.statusList)) {
|
},
|
||||||
responseSchema.statusList.push(newStatus);
|
);
|
||||||
} else {
|
|
||||||
responseSchema.statusList = [newStatus];
|
|
||||||
}
|
|
||||||
return this.responseSchemaRepository.save(responseSchema);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,7 +2,6 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class SurveyResponseService {
|
export class SurveyResponseService {
|
||||||
constructor(
|
constructor(
|
||||||
@ -36,14 +35,11 @@ export class SurveyResponseService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
async getSurveyResponseTotalByPath(surveyPath: string) {
|
async getSurveyResponseTotalByPath(surveyPath: string) {
|
||||||
const count = await this.surveyResponseRepository.count({
|
const data = await this.surveyResponseRepository.find({
|
||||||
where: {
|
where: {
|
||||||
surveyPath,
|
surveyPath,
|
||||||
'subStatus.status': {
|
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return count;
|
return (data || []).length;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,19 +1,18 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
|
|
||||||
import { MessageModule } from '../message/message.module';
|
import { MessageModule } from '../message/message.module';
|
||||||
|
// import { RedisModule } from '../redis/redis.module';
|
||||||
|
|
||||||
import { ResponseSchemaService } from './services/responseScheme.service';
|
import { ResponseSchemaService } from './services/responseScheme.service';
|
||||||
import { SurveyResponseService } from './services/surveyResponse.service';
|
import { SurveyResponseService } from './services/surveyResponse.service';
|
||||||
import { CounterService } from './services/counter.service';
|
import { CounterService } from './services/counter.service';
|
||||||
import { ClientEncryptService } from './services/clientEncrypt.service';
|
import { ClientEncryptService } from './services/clientEncrypt.service';
|
||||||
|
// import { RedisService } from '../redis/redis.service';
|
||||||
|
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { Counter } from 'src/models/counter.entity';
|
import { Counter } from 'src/models/counter.entity';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||||
import { Logger } from 'src/logger';
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
|
|
||||||
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
||||||
import { CounterController } from './controllers/counter.controller';
|
import { CounterController } from './controllers/counter.controller';
|
||||||
@ -23,6 +22,9 @@ import { SurveyResponseUIController } from './controllers/surveyResponseUI.contr
|
|||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||||
|
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
@ -33,6 +35,7 @@ import { WorkspaceModule } from '../workspace/workspace.module';
|
|||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
MessageModule,
|
MessageModule,
|
||||||
|
// RedisModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
WorkspaceModule,
|
WorkspaceModule,
|
||||||
],
|
],
|
||||||
@ -48,7 +51,8 @@ import { WorkspaceModule } from '../workspace/workspace.module';
|
|||||||
SurveyResponseService,
|
SurveyResponseService,
|
||||||
CounterService,
|
CounterService,
|
||||||
ClientEncryptService,
|
ClientEncryptService,
|
||||||
Logger,
|
LoggerProvider,
|
||||||
|
// RedisService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Controller, Get, HttpCode } from '@nestjs/common';
|
import { Controller, Get, HttpCode, Request } from '@nestjs/common';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { UpgradeService } from '../services/upgrade.service';
|
import { UpgradeService } from '../services/upgrade.service';
|
||||||
|
|
||||||
@ -7,12 +7,15 @@ import { UpgradeService } from '../services/upgrade.service';
|
|||||||
export class UpgradeController {
|
export class UpgradeController {
|
||||||
constructor(private readonly upgradeService: UpgradeService) {}
|
constructor(private readonly upgradeService: UpgradeService) {}
|
||||||
|
|
||||||
@Get('/subStatus')
|
@Get('/upgradeFeatureStatus')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async upgradeSubStatus() {
|
async upgradeSubStatus(@Request() req) {
|
||||||
await this.upgradeService.upgradeSubStatus();
|
this.upgradeService.upgradeFeatureStatus();
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
data: {
|
||||||
|
traceId: req.traceId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,73 +2,201 @@ import { Injectable } from '@nestjs/common';
|
|||||||
import { InjectRepository } from '@nestjs/typeorm';
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { ResponseSchema } from 'src/models/ResponseSchema.entity';
|
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||||
import { RECORD_STATUS, RECORD_SUB_STATUS } from 'src/enums';
|
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()
|
@Injectable()
|
||||||
export class UpgradeService {
|
export class UpgradeService {
|
||||||
constructor(
|
constructor(
|
||||||
@InjectRepository(SurveyMeta)
|
private readonly logger: Logger,
|
||||||
private readonly SurveyMeta: MongoRepository<SurveyMeta>,
|
@InjectRepository(Collaborator)
|
||||||
|
private readonly collaboratorRepository: MongoRepository<Collaborator>,
|
||||||
|
@InjectRepository(Counter)
|
||||||
|
private readonly counterRepository: MongoRepository<Counter>,
|
||||||
|
@InjectRepository(DownloadTask)
|
||||||
|
private readonly downloadTaskRepository: MongoRepository<DownloadTask>,
|
||||||
|
@InjectRepository(MessagePushingLog)
|
||||||
|
private readonly messagePushingLogRepository: MongoRepository<MessagePushingLog>,
|
||||||
|
@InjectRepository(MessagePushingTask)
|
||||||
|
private readonly messagePushingTaskRepository: MongoRepository<MessagePushingTask>,
|
||||||
@InjectRepository(ResponseSchema)
|
@InjectRepository(ResponseSchema)
|
||||||
private readonly ResponseSchema: MongoRepository<ResponseSchema>,
|
private readonly responseSchemaRepository: MongoRepository<ResponseSchema>,
|
||||||
|
@InjectRepository(Session)
|
||||||
|
private readonly sessionRepository: MongoRepository<Session>,
|
||||||
|
@InjectRepository(SurveyConf)
|
||||||
|
private readonly surveyConfRepository: MongoRepository<SurveyConf>,
|
||||||
|
@InjectRepository(SurveyMeta)
|
||||||
|
private readonly surveyMetaRepository: MongoRepository<SurveyMeta>,
|
||||||
|
@InjectRepository(User)
|
||||||
|
private readonly userRepository: MongoRepository<User>,
|
||||||
|
@InjectRepository(Workspace)
|
||||||
|
private readonly workspaceRepository: MongoRepository<Workspace>,
|
||||||
|
@InjectRepository(WorkspaceMember)
|
||||||
|
private readonly workspaceMemberRepository: MongoRepository<WorkspaceMember>,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
async upgradeSubStatus() {
|
async upgradeFeatureStatus() {
|
||||||
const surveyMetaList = await this.SurveyMeta.find();
|
const repositories = [
|
||||||
const responseSchemaList = await this.ResponseSchema.find();
|
this.collaboratorRepository,
|
||||||
|
this.counterRepository,
|
||||||
const callBack = (v: SurveyMeta | ResponseSchema) => {
|
this.downloadTaskRepository,
|
||||||
// 将主状态的REMOVED,EDITING刷到子状态
|
this.messagePushingLogRepository,
|
||||||
// 主状态查一下历史数据删除前最近的状态是“新建”or“已发布
|
this.messagePushingTaskRepository,
|
||||||
if (
|
this.responseSchemaRepository,
|
||||||
v.curStatus.status == (RECORD_SUB_STATUS.REMOVED as any) ||
|
this.sessionRepository,
|
||||||
v.curStatus.status == (RECORD_SUB_STATUS.EDITING as any)
|
this.surveyConfRepository,
|
||||||
) {
|
this.surveyMetaRepository,
|
||||||
const subStatus = {
|
this.userRepository,
|
||||||
status: v.curStatus.status,
|
this.workspaceRepository,
|
||||||
date: v.curStatus.date,
|
this.workspaceMemberRepository,
|
||||||
};
|
];
|
||||||
v.subStatus = subStatus as any;
|
const handleCreatedAtAndUpdatedAt = (doc) => {
|
||||||
console.log('subStatus', subStatus);
|
if (!doc.createdAt) {
|
||||||
if (v.curStatus.status == (RECORD_SUB_STATUS.EDITING as any)) {
|
if (doc.createDate) {
|
||||||
v.curStatus.status = RECORD_STATUS.PUBLISHED;
|
doc.createdAt = new Date(doc.createDate);
|
||||||
|
delete doc.createDate;
|
||||||
|
} else {
|
||||||
|
doc.createdAt = new Date();
|
||||||
}
|
}
|
||||||
if (v.curStatus.status == (RECORD_SUB_STATUS.REMOVED as any)) {
|
|
||||||
for (let index = v.statusList.length; index > 0; index--) {
|
|
||||||
const item = v.statusList[index];
|
|
||||||
if (
|
|
||||||
item?.status == RECORD_STATUS.PUBLISHED ||
|
|
||||||
item?.status == RECORD_STATUS.NEW
|
|
||||||
) {
|
|
||||||
v.curStatus.status = item.status;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return v;
|
|
||||||
}
|
}
|
||||||
|
if (!doc.updatedAt) {
|
||||||
|
if (doc.updateDate) {
|
||||||
|
doc.updatedAt = new Date(doc.updateDate);
|
||||||
|
delete doc.updateDate;
|
||||||
|
} else {
|
||||||
|
doc.createdAt = 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 (
|
if (
|
||||||
v.curStatus.status == RECORD_STATUS.PUBLISHED ||
|
!doc?.subStatus &&
|
||||||
v.curStatus.status == RECORD_STATUS.NEW
|
(doc?.curStatus?.status == RECORD_STATUS.PUBLISHED ||
|
||||||
|
doc?.curStatus?.status == RECORD_STATUS.NEW ||
|
||||||
|
doc?.curStatus?.status === RECORD_STATUS.EDITING)
|
||||||
) {
|
) {
|
||||||
const subStatus = {
|
const subStatus = {
|
||||||
status: RECORD_SUB_STATUS.DEFAULT,
|
status: RECORD_SUB_STATUS.DEFAULT,
|
||||||
date: v.statusList[0].date,
|
date: doc.curStatus.date,
|
||||||
};
|
};
|
||||||
v.subStatus = subStatus;
|
doc.subStatus = subStatus;
|
||||||
}
|
}
|
||||||
return v;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
surveyMetaList.map(async (v) => {
|
const handleBegTime = (doc) => {
|
||||||
const item = callBack(v);
|
if (!doc?.baseConf?.beginTime && doc?.baseConf?.begTime) {
|
||||||
await this.SurveyMeta.save(item);
|
doc.baseConf.beginTime = doc.baseConf.begTime;
|
||||||
});
|
delete doc.baseConf.begTime;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
responseSchemaList.map(async (v) => {
|
const handleSessionStatus = (doc) => {
|
||||||
const item = callBack(v);
|
if (!doc.status && doc.curStatus) {
|
||||||
await this.ResponseSchema.save(item);
|
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.save(entity);
|
||||||
|
};
|
||||||
|
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...`);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,36 +1,48 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
import { MessageModule } from '../message/message.module';
|
|
||||||
|
|
||||||
import { UpgradeService } from './services/upgrade.service';
|
import { UpgradeService } from './services/upgrade.service';
|
||||||
|
|
||||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
import { Counter } from 'src/models/counter.entity';
|
import { Counter } from 'src/models/counter.entity';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { DownloadTask } from 'src/models/downloadTask.entity';
|
||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { MessagePushingLog } from 'src/models/messagePushingLog.entity';
|
||||||
import { Logger } from 'src/logger';
|
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 { 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 { UpgradeController } from './controllers/upgrade.controller';
|
||||||
|
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
ResponseSchema,
|
Collaborator,
|
||||||
Counter,
|
Counter,
|
||||||
SurveyResponse,
|
DownloadTask,
|
||||||
ClientEncrypt,
|
MessagePushingLog,
|
||||||
|
MessagePushingTask,
|
||||||
|
ResponseSchema,
|
||||||
|
Session,
|
||||||
|
SurveyConf,
|
||||||
|
SurveyHistory,
|
||||||
SurveyMeta,
|
SurveyMeta,
|
||||||
|
SurveyResponse,
|
||||||
|
User,
|
||||||
|
Workspace,
|
||||||
|
WorkspaceMember,
|
||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
MessageModule,
|
|
||||||
AuthModule,
|
AuthModule,
|
||||||
WorkspaceModule,
|
|
||||||
],
|
],
|
||||||
controllers: [UpgradeController],
|
controllers: [UpgradeController],
|
||||||
providers: [UpgradeService, Logger],
|
providers: [UpgradeService, Logger],
|
||||||
|
@ -64,10 +64,7 @@ export class WorkspaceController {
|
|||||||
async create(@Body() workspace: CreateWorkspaceDto, @Request() req) {
|
async create(@Body() workspace: CreateWorkspaceDto, @Request() req) {
|
||||||
const { value, error } = CreateWorkspaceDto.validate(workspace);
|
const { value, error } = CreateWorkspaceDto.validate(workspace);
|
||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(`CreateWorkspaceDto validate failed: ${error.message}`);
|
||||||
`CreateWorkspaceDto validate failed: ${error.message}`,
|
|
||||||
{ req },
|
|
||||||
);
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
`参数错误: 请联系管理员`,
|
`参数错误: 请联系管理员`,
|
||||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
@ -103,10 +100,12 @@ export class WorkspaceController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const userId = req.user._id.toString();
|
const userId = req.user._id.toString();
|
||||||
|
const username = req.user.username;
|
||||||
// 插入空间表
|
// 插入空间表
|
||||||
const retWorkspace = await this.workspaceService.create({
|
const retWorkspace = await this.workspaceService.create({
|
||||||
name: value.name,
|
name: value.name,
|
||||||
description: value.description,
|
description: value.description,
|
||||||
|
owner: username,
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
});
|
});
|
||||||
const workspaceId = retWorkspace._id.toString();
|
const workspaceId = retWorkspace._id.toString();
|
||||||
@ -120,6 +119,8 @@ export class WorkspaceController {
|
|||||||
await this.workspaceMemberService.batchCreate({
|
await this.workspaceMemberService.batchCreate({
|
||||||
workspaceId,
|
workspaceId,
|
||||||
members: value.members,
|
members: value.members,
|
||||||
|
creator: username,
|
||||||
|
creatorId: userId,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
return {
|
return {
|
||||||
@ -137,7 +138,6 @@ export class WorkspaceController {
|
|||||||
if (error) {
|
if (error) {
|
||||||
this.logger.error(
|
this.logger.error(
|
||||||
`GetWorkspaceListDto validate failed: ${error.message}`,
|
`GetWorkspaceListDto validate failed: ${error.message}`,
|
||||||
{ req },
|
|
||||||
);
|
);
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
`参数错误: 请联系管理员`,
|
`参数错误: 请联系管理员`,
|
||||||
@ -210,7 +210,7 @@ export class WorkspaceController {
|
|||||||
const ownerInfo = userInfoMap?.[item.ownerId] || {};
|
const ownerInfo = userInfoMap?.[item.ownerId] || {};
|
||||||
return {
|
return {
|
||||||
...item,
|
...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,
|
owner: ownerInfo.username,
|
||||||
currentUserId: curWorkspaceInfo.userId,
|
currentUserId: curWorkspaceInfo.userId,
|
||||||
currentUserRole: curWorkspaceInfo.role,
|
currentUserRole: curWorkspaceInfo.role,
|
||||||
@ -272,13 +272,24 @@ export class WorkspaceController {
|
|||||||
@UseGuards(WorkspaceGuard)
|
@UseGuards(WorkspaceGuard)
|
||||||
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])
|
||||||
@SetMetadata('workspaceId', 'params.id')
|
@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;
|
const members = workspace.members;
|
||||||
if (!Array.isArray(members) || members.length === 0) {
|
if (!Array.isArray(members) || members.length === 0) {
|
||||||
throw new HttpException('成员不能为空', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('成员不能为空', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
delete workspace.members;
|
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)}`);
|
this.logger.info(`updateRes: ${JSON.stringify(updateRes)}`);
|
||||||
const { newMembers, adminMembers, userMembers } = splitMembers(members);
|
const { newMembers, adminMembers, userMembers } = splitMembers(members);
|
||||||
if (
|
if (
|
||||||
@ -324,14 +335,20 @@ export class WorkspaceController {
|
|||||||
this.workspaceMemberService.batchCreate({
|
this.workspaceMemberService.batchCreate({
|
||||||
workspaceId: id,
|
workspaceId: id,
|
||||||
members: newMembers,
|
members: newMembers,
|
||||||
|
creator: operator,
|
||||||
|
creatorId: operatorId,
|
||||||
}),
|
}),
|
||||||
this.workspaceMemberService.batchUpdate({
|
this.workspaceMemberService.batchUpdate({
|
||||||
idList: adminMembers,
|
idList: adminMembers,
|
||||||
role: WORKSPACE_ROLE.ADMIN,
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
}),
|
}),
|
||||||
this.workspaceMemberService.batchUpdate({
|
this.workspaceMemberService.batchUpdate({
|
||||||
idList: userMembers,
|
idList: userMembers,
|
||||||
role: WORKSPACE_ROLE.USER,
|
role: WORKSPACE_ROLE.USER,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
}),
|
}),
|
||||||
]);
|
]);
|
||||||
this.logger.info(`updateRes: ${JSON.stringify(res)}`);
|
this.logger.info(`updateRes: ${JSON.stringify(res)}`);
|
||||||
@ -345,8 +362,13 @@ export class WorkspaceController {
|
|||||||
@UseGuards(WorkspaceGuard)
|
@UseGuards(WorkspaceGuard)
|
||||||
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])
|
||||||
@SetMetadata('workspaceId', 'params.id')
|
@SetMetadata('workspaceId', 'params.id')
|
||||||
async delete(@Param('id') id: string) {
|
async delete(@Param('id') id: string, @Request() req) {
|
||||||
const res = await this.workspaceService.delete(id);
|
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)}`);
|
this.logger.info(`res: ${JSON.stringify(res)}`);
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -77,7 +77,10 @@ export class WorkspaceMemberController {
|
|||||||
@Post('updateRole')
|
@Post('updateRole')
|
||||||
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_MEMBER])
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_MEMBER])
|
||||||
@SetMetadata('workspaceId', 'body.workspaceId')
|
@SetMetadata('workspaceId', 'body.workspaceId')
|
||||||
async updateRole(@Body() updateDto: UpdateWorkspaceMemberDto) {
|
async updateRole(
|
||||||
|
@Body() updateDto: UpdateWorkspaceMemberDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
const { error, value } = UpdateWorkspaceMemberDto.validate(updateDto);
|
const { error, value } = UpdateWorkspaceMemberDto.validate(updateDto);
|
||||||
if (error) {
|
if (error) {
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
@ -85,10 +88,14 @@ export class WorkspaceMemberController {
|
|||||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
const operator = req.user.username,
|
||||||
|
operatorId = req.user._id.toString();
|
||||||
const updateRes = await this.workspaceMemberService.updateRole({
|
const updateRes = await this.workspaceMemberService.updateRole({
|
||||||
role: value.role,
|
role: value.role,
|
||||||
workspaceId: value.workspaceId,
|
workspaceId: value.workspaceId,
|
||||||
userId: value.userId,
|
userId: value.userId,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -6,7 +6,6 @@ import { Workspace } from 'src/models/workspace.entity';
|
|||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { RECORD_SUB_STATUS } from 'src/enums';
|
|
||||||
|
|
||||||
interface FindAllByIdWithPaginationParams {
|
interface FindAllByIdWithPaginationParams {
|
||||||
workspaceIdList: string[];
|
workspaceIdList: string[];
|
||||||
@ -31,10 +30,13 @@ export class WorkspaceService {
|
|||||||
async create(workspace: {
|
async create(workspace: {
|
||||||
name: string;
|
name: string;
|
||||||
description: string;
|
description: string;
|
||||||
|
owner: string;
|
||||||
ownerId: string;
|
ownerId: string;
|
||||||
}): Promise<Workspace> {
|
}): Promise<Workspace> {
|
||||||
const newWorkspace = this.workspaceRepository.create({
|
const newWorkspace = this.workspaceRepository.create({
|
||||||
...workspace,
|
...workspace,
|
||||||
|
creatorId: workspace.ownerId,
|
||||||
|
creator: workspace.owner,
|
||||||
});
|
});
|
||||||
return this.workspaceRepository.save(newWorkspace);
|
return this.workspaceRepository.save(newWorkspace);
|
||||||
}
|
}
|
||||||
@ -56,8 +58,8 @@ export class WorkspaceService {
|
|||||||
_id: {
|
_id: {
|
||||||
$in: workspaceIdList.map((item) => new ObjectId(item)),
|
$in: workspaceIdList.map((item) => new ObjectId(item)),
|
||||||
},
|
},
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -68,11 +70,11 @@ export class WorkspaceService {
|
|||||||
},
|
},
|
||||||
select: [
|
select: [
|
||||||
'_id',
|
'_id',
|
||||||
'curStatus',
|
|
||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'ownerId',
|
'ownerId',
|
||||||
'createDate',
|
'creatorId',
|
||||||
|
'createdAt',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -91,8 +93,8 @@ export class WorkspaceService {
|
|||||||
_id: {
|
_id: {
|
||||||
$in: workspaceIdList.map((m) => new ObjectId(m)),
|
$in: workspaceIdList.map((m) => new ObjectId(m)),
|
||||||
},
|
},
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
if (name) {
|
if (name) {
|
||||||
@ -103,31 +105,40 @@ export class WorkspaceService {
|
|||||||
skip,
|
skip,
|
||||||
take: limit,
|
take: limit,
|
||||||
order: {
|
order: {
|
||||||
createDate: -1,
|
createdAt: -1,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
return { list: data, count };
|
return { list: data, count };
|
||||||
}
|
}
|
||||||
|
|
||||||
update(id: string, workspace: Partial<Workspace>) {
|
update({
|
||||||
|
id,
|
||||||
|
workspace,
|
||||||
|
operator,
|
||||||
|
operatorId,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
workspace: Partial<Workspace>;
|
||||||
|
operator: string;
|
||||||
|
operatorId: string;
|
||||||
|
}) {
|
||||||
|
workspace.updatedAt = new Date();
|
||||||
|
workspace.operator = operator;
|
||||||
|
workspace.operatorId = operatorId;
|
||||||
return this.workspaceRepository.update(id, workspace);
|
return this.workspaceRepository.update(id, workspace);
|
||||||
}
|
}
|
||||||
|
|
||||||
async delete(id: string) {
|
async delete(id: string, { operator, operatorId }) {
|
||||||
const newStatus = {
|
|
||||||
status: RECORD_SUB_STATUS.REMOVED,
|
|
||||||
date: Date.now(),
|
|
||||||
};
|
|
||||||
const workspaceRes = await this.workspaceRepository.updateOne(
|
const workspaceRes = await this.workspaceRepository.updateOne(
|
||||||
{
|
{
|
||||||
_id: new ObjectId(id),
|
_id: new ObjectId(id),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
subStatus: newStatus,
|
isDeleted: true,
|
||||||
},
|
deletedAt: new Date(),
|
||||||
$push: {
|
operator,
|
||||||
statusList: newStatus as never,
|
operatorId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -137,10 +148,10 @@ export class WorkspaceService {
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
$set: {
|
$set: {
|
||||||
subStatus: newStatus,
|
isDeleted: true,
|
||||||
},
|
deletedAt: new Date(),
|
||||||
$push: {
|
operator,
|
||||||
statusList: newStatus as never,
|
operatorId,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
@ -155,8 +166,8 @@ export class WorkspaceService {
|
|||||||
return await this.workspaceRepository.find({
|
return await this.workspaceRepository.find({
|
||||||
where: {
|
where: {
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
'subStatus.status': {
|
isDeleted: {
|
||||||
$ne: RECORD_SUB_STATUS.REMOVED,
|
$ne: true,
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
order: {
|
order: {
|
||||||
@ -168,7 +179,7 @@ export class WorkspaceService {
|
|||||||
'name',
|
'name',
|
||||||
'description',
|
'description',
|
||||||
'ownerId',
|
'ownerId',
|
||||||
'createDate',
|
'createdAt',
|
||||||
],
|
],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user