feat: 新增数据推送功能 (#86)
This commit is contained in:
parent
b3b5fa9fac
commit
746bece538
@ -24,6 +24,7 @@
|
|||||||
"@nestjs/core": "^10.0.0",
|
"@nestjs/core": "^10.0.0",
|
||||||
"@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/typeorm": "^10.0.1",
|
"@nestjs/typeorm": "^10.0.1",
|
||||||
"cheerio": "^1.0.0-rc.12",
|
"cheerio": "^1.0.0-rc.12",
|
||||||
"crypto-js": "^4.2.0",
|
"crypto-js": "^4.2.0",
|
||||||
@ -35,6 +36,7 @@
|
|||||||
"moment": "^2.30.1",
|
"moment": "^2.30.1",
|
||||||
"mongodb": "^5.9.2",
|
"mongodb": "^5.9.2",
|
||||||
"nanoid": "^3.3.7",
|
"nanoid": "^3.3.7",
|
||||||
|
"node-fetch": "^2.7.0",
|
||||||
"node-forge": "^1.3.1",
|
"node-forge": "^1.3.1",
|
||||||
"reflect-metadata": "^0.1.13",
|
"reflect-metadata": "^0.1.13",
|
||||||
"rxjs": "^7.8.1",
|
"rxjs": "^7.8.1",
|
||||||
|
@ -27,6 +27,8 @@ import { Counter } from './models/counter.entity';
|
|||||||
import { SurveyResponse } from './models/surveyResponse.entity';
|
import { SurveyResponse } from './models/surveyResponse.entity';
|
||||||
import { ClientEncrypt } from './models/clientEncrypt.entity';
|
import { ClientEncrypt } from './models/clientEncrypt.entity';
|
||||||
import { Word } from './models/word.entity';
|
import { Word } from './models/word.entity';
|
||||||
|
import { MessagePushingTask } from './models/messagePushingTask.entity';
|
||||||
|
import { MessagePushingLog } from './models/messagePushingLog.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';
|
||||||
@ -69,6 +71,8 @@ import { Logger } from './logger';
|
|||||||
ResponseSchema,
|
ResponseSchema,
|
||||||
ClientEncrypt,
|
ClientEncrypt,
|
||||||
Word,
|
Word,
|
||||||
|
MessagePushingTask,
|
||||||
|
MessagePushingLog,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
7
server/src/enums/messagePushing.ts
Normal file
7
server/src/enums/messagePushing.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export enum MESSAGE_PUSHING_TYPE {
|
||||||
|
HTTP = 'http',
|
||||||
|
}
|
||||||
|
|
||||||
|
export enum MESSAGE_PUSHING_HOOK {
|
||||||
|
RESPONSE_INSERTED = 'response_inserted',
|
||||||
|
}
|
@ -1,13 +1,12 @@
|
|||||||
import * as log4js from 'log4js';
|
import * as log4js from 'log4js';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
import { REQUEST } from '@nestjs/core';
|
import { Request } from 'express';
|
||||||
import { Inject, Request } from '@nestjs/common';
|
|
||||||
const log4jsLogger = log4js.getLogger();
|
const log4jsLogger = log4js.getLogger();
|
||||||
|
|
||||||
export class Logger {
|
export class Logger {
|
||||||
private static inited = false;
|
private static inited = false;
|
||||||
|
|
||||||
constructor(@Inject(REQUEST) private req: Request) {}
|
constructor() {}
|
||||||
|
|
||||||
static init(config: { filename: string }) {
|
static init(config: { filename: string }) {
|
||||||
if (this.inited) {
|
if (this.inited) {
|
||||||
@ -33,23 +32,23 @@ export class Logger {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
_log(message, options: { dltag?: string; level: string }) {
|
_log(message, options: { dltag?: string; level: string; req?: Request }) {
|
||||||
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 = this.req['traceId']
|
const traceIdStr = options?.req['traceId']
|
||||||
? `traceid=${this.req['traceId']}||`
|
? `traceid=${options?.req['traceId']}||`
|
||||||
: '';
|
: '';
|
||||||
return log4jsLogger[level](
|
return log4jsLogger[level](
|
||||||
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
info(message, options = { dltag: '' }) {
|
info(message, options?: { dltag?: string; req?: Request }) {
|
||||||
return this._log(message, { ...options, level: 'info' });
|
return this._log(message, { ...options, level: 'info' });
|
||||||
}
|
}
|
||||||
|
|
||||||
error(message, options = { dltag: '' }) {
|
error(message, options: { dltag?: string; req?: Request }) {
|
||||||
return this._log(message, { ...options, level: 'error' });
|
return this._log(message, { ...options, level: 'error' });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,9 +1,26 @@
|
|||||||
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';
|
||||||
|
|
||||||
async function bootstrap() {
|
async function bootstrap() {
|
||||||
const PORT = process.env.PORT || 3000;
|
const PORT = process.env.PORT || 3000;
|
||||||
const app = await NestFactory.create(AppModule);
|
const app = await NestFactory.create(AppModule);
|
||||||
|
|
||||||
|
const config = new DocumentBuilder()
|
||||||
|
.setTitle('XIAOJU SURVEY')
|
||||||
|
.setDescription('')
|
||||||
|
.setVersion('1.0')
|
||||||
|
.addTag('auth')
|
||||||
|
.addTag('survey')
|
||||||
|
.addTag('surveyResponse')
|
||||||
|
.addTag('messagePushingTasks')
|
||||||
|
.addTag('ui')
|
||||||
|
.addBearerAuth()
|
||||||
|
.build();
|
||||||
|
|
||||||
|
const document = SwaggerModule.createDocument(app, config);
|
||||||
|
SwaggerModule.setup('swagger', app, document);
|
||||||
|
|
||||||
await app.listen(PORT);
|
await app.listen(PORT);
|
||||||
console.log(`server is running at: http://127.0.0.1:${PORT}`);
|
console.log(`server is running at: http://127.0.0.1:${PORT}`);
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ 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,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -29,6 +30,7 @@ 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,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -25,6 +25,6 @@ describe('BaseEntity', () => {
|
|||||||
const now = Date.now();
|
const now = Date.now();
|
||||||
baseEntity.onUpdate();
|
baseEntity.onUpdate();
|
||||||
|
|
||||||
expect(baseEntity.updateDate).toBeCloseTo(now, -3); // Check if date is close to current time
|
expect(baseEntity.updateDate).toBeCloseTo(now, -3);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
17
server/src/models/messagePushingLog.entity.ts
Normal file
17
server/src/models/messagePushingLog.entity.ts
Normal file
@ -0,0 +1,17 @@
|
|||||||
|
import { Entity, Column } from 'typeorm';
|
||||||
|
import { BaseEntity } from './base.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'messagePushingLog' })
|
||||||
|
export class MessagePushingLog extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
taskId: string;
|
||||||
|
|
||||||
|
@Column('jsonb')
|
||||||
|
request: Record<string, any>;
|
||||||
|
|
||||||
|
@Column('jsonb')
|
||||||
|
response: Record<string, any>;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
status: number; // http状态码
|
||||||
|
}
|
30
server/src/models/messagePushingTask.entity.ts
Normal file
30
server/src/models/messagePushingTask.entity.ts
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
import { Entity, Column } from 'typeorm';
|
||||||
|
import { BaseEntity } from './base.entity';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
@Entity({ name: 'messagePushingTask' })
|
||||||
|
export class MessagePushingTask extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
type: MESSAGE_PUSHING_TYPE;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
pushAddress: string; // 如果是http推送,则是http的链接
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK;
|
||||||
|
|
||||||
|
@Column('jsonb')
|
||||||
|
surveys: Array<string>;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
creatorId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
ownerId: string;
|
||||||
|
}
|
@ -82,8 +82,6 @@ describe('CaptchaService', () => {
|
|||||||
|
|
||||||
expect(captchaRepository.delete).toHaveBeenCalledWith(mockCaptchaId);
|
expect(captchaRepository.delete).toHaveBeenCalledWith(mockCaptchaId);
|
||||||
});
|
});
|
||||||
|
|
||||||
// Add more test cases for different scenarios
|
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('checkCaptchaIsCorrect', () => {
|
describe('checkCaptchaIsCorrect', () => {
|
||||||
|
@ -1,14 +1,18 @@
|
|||||||
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
||||||
import { UserService } from '../services/user.service';
|
|
||||||
import { CaptchaService } from '../services/captcha.service'; // 假设你的验证码服务在这里
|
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { UserService } from '../services/user.service';
|
||||||
|
<<<<<<< Updated upstream
|
||||||
|
import { CaptchaService } from '../services/captcha.service'; // 假设你的验证码服务在这里
|
||||||
|
=======
|
||||||
|
import { CaptchaService } from '../services/captcha.service';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
>>>>>>> Stashed changes
|
||||||
import { AuthService } from '../services/auth.service';
|
import { AuthService } from '../services/auth.service';
|
||||||
|
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
|
||||||
import { create } from 'svg-captcha';
|
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { create } from 'svg-captcha';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
@ApiTags('auth')
|
||||||
@Controller('/api/auth')
|
@Controller('/api/auth')
|
||||||
export class AuthController {
|
export class AuthController {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -0,0 +1,47 @@
|
|||||||
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
describe('CreateMessagePushingTaskDto', () => {
|
||||||
|
let dto: CreateMessagePushingTaskDto;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new CreateMessagePushingTaskDto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a name', () => {
|
||||||
|
dto.name = '';
|
||||||
|
expect(dto.name).toBeDefined();
|
||||||
|
expect(dto.name).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a valid type', () => {
|
||||||
|
dto.type = MESSAGE_PUSHING_TYPE.HTTP;
|
||||||
|
expect(dto.type).toBeDefined();
|
||||||
|
expect(dto.type).toEqual(MESSAGE_PUSHING_TYPE.HTTP);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a push address', () => {
|
||||||
|
dto.pushAddress = '';
|
||||||
|
expect(dto.pushAddress).toBeDefined();
|
||||||
|
expect(dto.pushAddress).toBe('');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a valid trigger hook', () => {
|
||||||
|
dto.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED;
|
||||||
|
expect(dto.triggerHook).toBeDefined();
|
||||||
|
expect(dto.triggerHook).toEqual(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an array of surveys', () => {
|
||||||
|
dto.surveys = ['survey1', 'survey2'];
|
||||||
|
expect(dto.surveys).toBeDefined();
|
||||||
|
expect(dto.surveys).toEqual(['survey1', 'survey2']);
|
||||||
|
});
|
||||||
|
});
|
@ -1,17 +1,18 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
import { DataStatisticController } from '../controllers/dataStatistic.controller';
|
import { DataStatisticController } from '../controllers/dataStatistic.controller';
|
||||||
import { DataStatisticService } from '../services/dataStatistic.service';
|
import { DataStatisticService } from '../services/dataStatistic.service';
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
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 { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
import { Authtication } from 'src/guards/authtication';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
|
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
|
|
||||||
jest.mock('../services/dataStatistic.service');
|
jest.mock('../services/dataStatistic.service');
|
||||||
jest.mock('../services/surveyMeta.service');
|
jest.mock('../services/surveyMeta.service');
|
||||||
jest.mock('../../surveyResponse/services/responseScheme.service');
|
jest.mock('../../surveyResponse/services/responseScheme.service');
|
||||||
|
@ -0,0 +1,228 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { MessagePushingTaskController } from '../controllers/messagePushingTask.controller';
|
||||||
|
import { MessagePushingTaskService } from '../services/messagePushingTask.service';
|
||||||
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
|
import { QueryMessagePushingTaskListDto } from '../dto/queryMessagePushingTaskList.dto';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
|
import { Authtication } from 'src/guards/authtication';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
|
||||||
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
describe('MessagePushingTaskController', () => {
|
||||||
|
let controller: MessagePushingTaskController;
|
||||||
|
let service: MessagePushingTaskService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [MessagePushingTaskController],
|
||||||
|
providers: [
|
||||||
|
ConfigService,
|
||||||
|
{
|
||||||
|
provide: MessagePushingTaskService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
findAll: jest.fn(),
|
||||||
|
findOne: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
remove: jest.fn(),
|
||||||
|
surveyAuthorizeTask: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Authtication,
|
||||||
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
|
canActivate: () => true,
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
|
getUserByUsername() {
|
||||||
|
return {};
|
||||||
|
},
|
||||||
|
})),
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<MessagePushingTaskController>(
|
||||||
|
MessagePushingTaskController,
|
||||||
|
);
|
||||||
|
service = module.get<MessagePushingTaskService>(MessagePushingTaskService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a message pushing task', async () => {
|
||||||
|
const createDto: CreateMessagePushingTaskDto = {
|
||||||
|
name: 'test name',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: 'http://example.com',
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
surveys: [],
|
||||||
|
};
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '66028642292c50f8b71a9eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mockTask = {
|
||||||
|
_id: new ObjectId(),
|
||||||
|
name: 'test name',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: 'http://example.com',
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
surveys: [],
|
||||||
|
} as MessagePushingTask;
|
||||||
|
jest.spyOn(service, 'create').mockResolvedValueOnce(mockTask);
|
||||||
|
|
||||||
|
const result = await controller.create(req, createDto);
|
||||||
|
expect(service.create).toHaveBeenCalledWith({
|
||||||
|
...createDto,
|
||||||
|
ownerId: req.user._id,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: { taskId: mockTask._id.toString() },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAll', () => {
|
||||||
|
it('should find all message pushing tasks by surveyId and triggerHook', async () => {
|
||||||
|
const queryDto: QueryMessagePushingTaskListDto = {
|
||||||
|
surveyId: '65f29f3192862d6a9067ad1c',
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
};
|
||||||
|
const mockList = [];
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '66028642292c50f8b71a9eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
jest.spyOn(service, 'findAll').mockResolvedValueOnce(mockList);
|
||||||
|
|
||||||
|
const result = await controller.findAll(req, queryDto);
|
||||||
|
expect(service.findAll).toHaveBeenCalledWith({
|
||||||
|
surveyId: queryDto.surveyId,
|
||||||
|
hook: queryDto.triggerHook,
|
||||||
|
ownerId: req.user._id,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ code: 200, data: mockList });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw HttpException if surveyId or triggerHook is missing', async () => {
|
||||||
|
const queryDto = {
|
||||||
|
surveyId: '',
|
||||||
|
triggerHook: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.findAll(req, queryDto as QueryMessagePushingTaskListDto),
|
||||||
|
).rejects.toThrow(
|
||||||
|
new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR),
|
||||||
|
);
|
||||||
|
expect(service.findAll).toHaveBeenCalledTimes(0);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', () => {
|
||||||
|
it('should find one message pushing task by ID', async () => {
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '66028642292c50f8b71a9eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mockTask = {} as MessagePushingTask; // create mock data for your test
|
||||||
|
jest.spyOn(service, 'findOne').mockResolvedValueOnce(mockTask);
|
||||||
|
const mockTaskId = '65afc62904d5db18534c0f78';
|
||||||
|
const result = await controller.findOne(req, mockTaskId);
|
||||||
|
expect(service.findOne).toHaveBeenCalledWith({
|
||||||
|
ownerId: req.user._id,
|
||||||
|
id: mockTaskId,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ code: 200, data: mockTask });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update a message pushing task by ID', async () => {
|
||||||
|
const updateDto: UpdateMessagePushingTaskDto = {
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
};
|
||||||
|
const mockTask = {}; // create mock data for your test
|
||||||
|
jest
|
||||||
|
.spyOn(service, 'update')
|
||||||
|
.mockResolvedValueOnce(mockTask as MessagePushingTask);
|
||||||
|
const mockTaskId = '65afc62904d5db18534c0f78';
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '66028642292c50f8b71a9eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const result = await controller.update(req, mockTaskId, updateDto);
|
||||||
|
expect(service.update).toHaveBeenCalledWith({
|
||||||
|
id: mockTaskId,
|
||||||
|
ownerId: req.user._id,
|
||||||
|
updateData: updateDto,
|
||||||
|
});
|
||||||
|
expect(result).toEqual({ code: 200, data: mockTask });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
it('should remove a message pushing task by ID', async () => {
|
||||||
|
const mockResponse = { modifiedCount: 1 };
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '66028642292c50f8b71a9eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const mockTaskId = '65afc62904d5db18534c0f78';
|
||||||
|
jest.spyOn(service, 'remove').mockResolvedValueOnce(mockResponse);
|
||||||
|
|
||||||
|
const result = await controller.remove(req, mockTaskId);
|
||||||
|
expect(result).toEqual({ code: 200, data: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('surveyAuthorizeTask', () => {
|
||||||
|
it('should authorize a survey for a task', async () => {
|
||||||
|
const mockResponse = { modifiedCount: 1 };
|
||||||
|
const req = {
|
||||||
|
user: {
|
||||||
|
_id: '66028642292c50f8b71a9eee',
|
||||||
|
},
|
||||||
|
};
|
||||||
|
jest
|
||||||
|
.spyOn(service, 'surveyAuthorizeTask')
|
||||||
|
.mockResolvedValueOnce(mockResponse);
|
||||||
|
const mockTaskId = '65afc62904d5db18534c0f78';
|
||||||
|
const mockSurveyId = '65f29f3192862d6a9067ad1c';
|
||||||
|
const result = await controller.surveyAuthorizeTask(
|
||||||
|
req,
|
||||||
|
mockTaskId,
|
||||||
|
mockSurveyId,
|
||||||
|
);
|
||||||
|
expect(result).toEqual({ code: 200, data: true });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
141
server/src/modules/survey/__test/messagePushingTask.dto.spec.ts
Normal file
141
server/src/modules/survey/__test/messagePushingTask.dto.spec.ts
Normal file
@ -0,0 +1,141 @@
|
|||||||
|
import {
|
||||||
|
MessagePushingTaskDto,
|
||||||
|
CodeDto,
|
||||||
|
MessagePushingTaskSucceedResponseDto,
|
||||||
|
MessagePushingTaskListSucceedResponse,
|
||||||
|
} from '../dto/messagePushingTask.dto';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
describe('MessagePushingTaskDto', () => {
|
||||||
|
let dto: MessagePushingTaskDto;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new MessagePushingTaskDto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an id', () => {
|
||||||
|
dto._id = 'test_id';
|
||||||
|
expect(dto._id).toBeDefined();
|
||||||
|
expect(dto._id).toBe('test_id');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a name', () => {
|
||||||
|
dto.name = 'test_name';
|
||||||
|
expect(dto.name).toBeDefined();
|
||||||
|
expect(dto.name).toBe('test_name');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a type', () => {
|
||||||
|
dto.type = MESSAGE_PUSHING_TYPE.HTTP; // Set your desired type here
|
||||||
|
expect(dto.type).toBeDefined();
|
||||||
|
expect(dto.type).toEqual(MESSAGE_PUSHING_TYPE.HTTP); // Adjust based on your enum
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a push address', () => {
|
||||||
|
dto.pushAddress = 'test_address';
|
||||||
|
expect(dto.pushAddress).toBeDefined();
|
||||||
|
expect(dto.pushAddress).toBe('test_address');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a trigger hook', () => {
|
||||||
|
dto.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED; // Set your desired hook here
|
||||||
|
expect(dto.triggerHook).toBeDefined();
|
||||||
|
expect(dto.triggerHook).toEqual(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED); // Adjust based on your enum
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an array of surveys', () => {
|
||||||
|
dto.surveys = ['survey1', 'survey2']; // Set your desired surveys here
|
||||||
|
expect(dto.surveys).toBeDefined();
|
||||||
|
expect(dto.surveys).toEqual(['survey1', 'survey2']);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have an owner', () => {
|
||||||
|
dto.owner = 'test_owner';
|
||||||
|
expect(dto.owner).toBeDefined();
|
||||||
|
expect(dto.owner).toBe('test_owner');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have current status', () => {
|
||||||
|
dto.curStatus = { status: RECORD_STATUS.NEW, date: Date.now() };
|
||||||
|
expect(dto.curStatus).toBeDefined();
|
||||||
|
expect(dto.curStatus.status).toEqual(RECORD_STATUS.NEW);
|
||||||
|
expect(dto.curStatus.date).toBeDefined();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('CodeDto', () => {
|
||||||
|
let dto: CodeDto;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new CodeDto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a code', () => {
|
||||||
|
dto.code = 200;
|
||||||
|
expect(dto.code).toBeDefined();
|
||||||
|
expect(dto.code).toBe(200);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MessagePushingTaskSucceedResponseDto', () => {
|
||||||
|
let dto: MessagePushingTaskSucceedResponseDto;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new MessagePushingTaskSucceedResponseDto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a code', () => {
|
||||||
|
dto.code = 200;
|
||||||
|
expect(dto.code).toBeDefined();
|
||||||
|
expect(dto.code).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have data', () => {
|
||||||
|
const taskDto = new MessagePushingTaskDto();
|
||||||
|
dto.data = taskDto;
|
||||||
|
expect(dto.data).toBeDefined();
|
||||||
|
expect(dto.data).toBeInstanceOf(MessagePushingTaskDto);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('MessagePushingTaskListSucceedResponse', () => {
|
||||||
|
let dto: MessagePushingTaskListSucceedResponse;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new MessagePushingTaskListSucceedResponse();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a code', () => {
|
||||||
|
dto.code = 200;
|
||||||
|
expect(dto.code).toBeDefined();
|
||||||
|
expect(dto.code).toBe(200);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have data', () => {
|
||||||
|
const taskDto = new MessagePushingTaskDto();
|
||||||
|
dto.data = [taskDto];
|
||||||
|
expect(dto.data).toBeDefined();
|
||||||
|
expect(dto.data).toBeInstanceOf(Array);
|
||||||
|
expect(dto.data[0]).toBeInstanceOf(MessagePushingTaskDto);
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,255 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { MessagePushingTaskService } from '../services/messagePushingTask.service';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
||||||
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
describe('MessagePushingTaskService', () => {
|
||||||
|
let service: MessagePushingTaskService;
|
||||||
|
let repository: MongoRepository<MessagePushingTask>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
MessagePushingTaskService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(MessagePushingTask),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<MessagePushingTaskService>(MessagePushingTaskService);
|
||||||
|
repository = module.get<MongoRepository<MessagePushingTask>>(
|
||||||
|
getRepositoryToken(MessagePushingTask),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a new message pushing task', async () => {
|
||||||
|
const createDto: CreateMessagePushingTaskDto = {
|
||||||
|
name: 'Test Task',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: 'http://example.com',
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
surveys: ['surveyId1', 'surveyId2'],
|
||||||
|
};
|
||||||
|
|
||||||
|
const savedTask = new MessagePushingTask();
|
||||||
|
savedTask.name = 'Test Task';
|
||||||
|
savedTask.type = MESSAGE_PUSHING_TYPE.HTTP;
|
||||||
|
savedTask.pushAddress = 'http://example.com';
|
||||||
|
savedTask.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED;
|
||||||
|
savedTask.surveys = ['surveyId1', 'surveyId2'];
|
||||||
|
|
||||||
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'create').mockReturnValue(savedTask);
|
||||||
|
jest.spyOn(repository, 'save').mockResolvedValue(savedTask);
|
||||||
|
|
||||||
|
const result = await service.create({
|
||||||
|
...createDto,
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(savedTask);
|
||||||
|
expect(repository.create).toHaveBeenCalledWith({
|
||||||
|
...createDto,
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
});
|
||||||
|
expect(repository.save).toHaveBeenCalledWith(savedTask);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAll', () => {
|
||||||
|
it('should find message pushing tasks by survey id and trigger hook', async () => {
|
||||||
|
const surveyId = 'surveyId';
|
||||||
|
const hook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED;
|
||||||
|
const tasks = [
|
||||||
|
{
|
||||||
|
_id: new ObjectId(),
|
||||||
|
name: 'Task 1',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: '',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: new ObjectId(),
|
||||||
|
name: 'Task 2',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: '',
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'find')
|
||||||
|
.mockResolvedValue(tasks as MessagePushingTask[]);
|
||||||
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
|
|
||||||
|
const result = await service.findAll({
|
||||||
|
surveyId,
|
||||||
|
hook,
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(tasks);
|
||||||
|
expect(repository.find).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
surveys: { $all: [surveyId] },
|
||||||
|
triggerHook: hook,
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', () => {
|
||||||
|
it('should find a message pushing task by id', async () => {
|
||||||
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
const task = { _id: new ObjectId(), name: 'Test Task' };
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'findOne')
|
||||||
|
.mockResolvedValue(task as MessagePushingTask);
|
||||||
|
|
||||||
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
|
const result = await service.findOne({
|
||||||
|
id: taskId,
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(task);
|
||||||
|
expect(repository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update a message pushing task', async () => {
|
||||||
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
const updateDto: UpdateMessagePushingTaskDto = {
|
||||||
|
name: 'Updated Task',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: 'http://update.example.com',
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
surveys: ['new survey id'],
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.EDITING,
|
||||||
|
date: Date.now(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
const existingTask = new MessagePushingTask();
|
||||||
|
existingTask._id = new ObjectId(taskId);
|
||||||
|
existingTask.name = 'Original Task';
|
||||||
|
const updatedTask = Object.assign({}, existingTask, updateDto);
|
||||||
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'findOne').mockResolvedValue(existingTask);
|
||||||
|
jest.spyOn(repository, 'save').mockResolvedValue(updatedTask);
|
||||||
|
|
||||||
|
const result = await service.update({
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
id: taskId,
|
||||||
|
updateData: updateDto,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(updatedTask);
|
||||||
|
expect(repository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(repository.save).toHaveBeenCalledWith(updatedTask);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('remove', () => {
|
||||||
|
it('should remove a message pushing task', async () => {
|
||||||
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
|
||||||
|
const updateResult = { modifiedCount: 1 };
|
||||||
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
||||||
|
|
||||||
|
const result = await service.remove({
|
||||||
|
id: taskId,
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(updateResult);
|
||||||
|
expect(repository.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: expect.any(Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: expect.any(Number),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('surveyAuthorizeTask', () => {
|
||||||
|
it('should authorize a survey for a task', async () => {
|
||||||
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
const surveyId = '65af380475b64545e5277dd9';
|
||||||
|
const mockOwnerId = '66028642292c50f8b71a9eee';
|
||||||
|
|
||||||
|
const updateResult = { modifiedCount: 1 };
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'updateOne').mockResolvedValue(updateResult);
|
||||||
|
|
||||||
|
const result = await service.surveyAuthorizeTask({
|
||||||
|
taskId,
|
||||||
|
surveyId,
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(updateResult);
|
||||||
|
expect(repository.updateOne).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
ownerId: mockOwnerId,
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
surveys: { $nin: [surveyId] },
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$push: {
|
||||||
|
surveys: surveyId,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,26 @@
|
|||||||
|
import { QueryMessagePushingTaskListDto } from '../dto/queryMessagePushingTaskList.dto';
|
||||||
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
describe('QueryMessagePushingTaskListDto', () => {
|
||||||
|
let dto: QueryMessagePushingTaskListDto;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new QueryMessagePushingTaskListDto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a surveyId', () => {
|
||||||
|
dto.surveyId = 'surveyId';
|
||||||
|
expect(dto.surveyId).toBeDefined();
|
||||||
|
expect(dto.surveyId).toBe('surveyId');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a triggerHook', () => {
|
||||||
|
dto.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED; // Set your desired hook here
|
||||||
|
expect(dto.triggerHook).toBeDefined();
|
||||||
|
expect(dto.triggerHook).toEqual(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED); // Adjust based on your enum
|
||||||
|
});
|
||||||
|
});
|
@ -10,8 +10,8 @@ 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 { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
|
|
||||||
// Mock the services
|
|
||||||
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');
|
||||||
@ -37,6 +37,7 @@ describe('SurveyController', () => {
|
|||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
ContentSecurityService,
|
ContentSecurityService,
|
||||||
SurveyHistoryService,
|
SurveyHistoryService,
|
||||||
|
LoggerProvider,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
|
@ -87,7 +87,6 @@ describe('SurveyConfService', () => {
|
|||||||
code: schema,
|
code: schema,
|
||||||
} as unknown as SurveyConf);
|
} as unknown as SurveyConf);
|
||||||
|
|
||||||
// 调用待测试的方法
|
|
||||||
await service.saveSurveyConf({ surveyId, schema });
|
await service.saveSurveyConf({ surveyId, schema });
|
||||||
|
|
||||||
// 验证save方法被调用了一次,并且传入了正确的参数
|
// 验证save方法被调用了一次,并且传入了正确的参数
|
||||||
@ -120,7 +119,6 @@ describe('SurveyConfService', () => {
|
|||||||
expect(surveyConfRepository.save).not.toHaveBeenCalled();
|
expect(surveyConfRepository.save).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
// getSurveyContentByCode方法的单元测试
|
|
||||||
it('should get survey content by code', async () => {
|
it('should get survey content by code', async () => {
|
||||||
// 准备参数和模拟数据
|
// 准备参数和模拟数据
|
||||||
const schema = {
|
const schema = {
|
||||||
|
@ -115,6 +115,10 @@ describe('SurveyHistoryService', () => {
|
|||||||
type: historyType,
|
type: historyType,
|
||||||
},
|
},
|
||||||
take: 100,
|
take: 100,
|
||||||
|
order: {
|
||||||
|
createDate: -1,
|
||||||
|
},
|
||||||
|
select: ['createDate', 'operator', 'type', '_id'],
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,8 +2,10 @@ 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 { Authtication } from 'src/guards/authtication';
|
import { Authtication } from 'src/guards/authtication';
|
||||||
import * as Joi from 'joi';
|
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
|
||||||
describe('SurveyMetaController', () => {
|
describe('SurveyMetaController', () => {
|
||||||
let controller: SurveyMetaController;
|
let controller: SurveyMetaController;
|
||||||
@ -23,6 +25,7 @@ describe('SurveyMetaController', () => {
|
|||||||
.mockResolvedValue({ count: 0, data: [] }),
|
.mockResolvedValue({ count: 0, data: [] }),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
LoggerProvider,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
.overrideGuard(Authtication)
|
.overrideGuard(Authtication)
|
||||||
@ -74,9 +77,7 @@ describe('SurveyMetaController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should validate request body with Joi', async () => {
|
it('should validate request body with Joi', async () => {
|
||||||
const reqBody = {
|
const reqBody = {};
|
||||||
// Missing title and surveyId
|
|
||||||
};
|
|
||||||
const req = {
|
const req = {
|
||||||
user: {
|
user: {
|
||||||
username: 'test-user',
|
username: 'test-user',
|
||||||
@ -86,8 +87,8 @@ describe('SurveyMetaController', () => {
|
|||||||
try {
|
try {
|
||||||
await controller.updateMeta(reqBody, req);
|
await controller.updateMeta(reqBody, req);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
expect(error).toBeInstanceOf(Joi.ValidationError);
|
expect(error).toBeInstanceOf(HttpException);
|
||||||
expect(error.details[0].message).toMatch('"title" is required');
|
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(surveyMetaService.checkSurveyAccess).not.toHaveBeenCalled();
|
expect(surveyMetaService.checkSurveyAccess).not.toHaveBeenCalled();
|
||||||
|
@ -0,0 +1,45 @@
|
|||||||
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
describe('UpdateMessagePushingTaskDto', () => {
|
||||||
|
let dto: UpdateMessagePushingTaskDto;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
dto = new UpdateMessagePushingTaskDto();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(dto).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a nullable name', () => {
|
||||||
|
dto.name = null;
|
||||||
|
expect(dto.name).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a nullable type', () => {
|
||||||
|
dto.type = null;
|
||||||
|
expect(dto.type).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a nullable push address', () => {
|
||||||
|
dto.pushAddress = null;
|
||||||
|
expect(dto.pushAddress).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a triggerHook', () => {
|
||||||
|
dto.triggerHook = MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED;
|
||||||
|
expect(dto.triggerHook).toBeDefined();
|
||||||
|
expect(dto.triggerHook).toEqual(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a nullable array of surveys', () => {
|
||||||
|
dto.surveys = null;
|
||||||
|
expect(dto.surveys).toBeNull();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should have a nullable curStatus', () => {
|
||||||
|
dto.curStatus = null;
|
||||||
|
expect(dto.curStatus).toBeNull();
|
||||||
|
});
|
||||||
|
});
|
@ -12,9 +12,11 @@ import { SurveyMetaService } from '../services/surveyMeta.service';
|
|||||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
import { Authtication } from 'src/guards/authtication';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
|
|
||||||
|
@ApiTags('survey')
|
||||||
@Controller('/api/survey/dataStatistic')
|
@Controller('/api/survey/dataStatistic')
|
||||||
export class DataStatisticController {
|
export class DataStatisticController {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -0,0 +1,170 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
Put,
|
||||||
|
Delete,
|
||||||
|
Query,
|
||||||
|
UseGuards,
|
||||||
|
Request,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiResponse, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { MessagePushingTaskService } from '../services/messagePushingTask.service';
|
||||||
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
import {
|
||||||
|
MessagePushingTaskSucceedResponseDto,
|
||||||
|
MessagePushingTaskListSucceedResponse,
|
||||||
|
CodeDto,
|
||||||
|
TaskIdDto,
|
||||||
|
} from '../dto/messagePushingTask.dto';
|
||||||
|
import { QueryMessagePushingTaskListDto } from '../dto/queryMessagePushingTaskList.dto';
|
||||||
|
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { Authtication } from 'src/guards/authtication';
|
||||||
|
|
||||||
|
@UseGuards(Authtication)
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@ApiTags('messagePushingTasks')
|
||||||
|
@Controller('/api/messagePushingTasks')
|
||||||
|
export class MessagePushingTaskController {
|
||||||
|
constructor(
|
||||||
|
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
description: '创建的推送任务',
|
||||||
|
status: 200,
|
||||||
|
type: TaskIdDto,
|
||||||
|
})
|
||||||
|
@Post('')
|
||||||
|
async create(
|
||||||
|
@Request()
|
||||||
|
req,
|
||||||
|
@Body() createMessagePushingTaskDto: CreateMessagePushingTaskDto,
|
||||||
|
) {
|
||||||
|
const userId = req.user._id;
|
||||||
|
|
||||||
|
const messagePushingTask = await this.messagePushingTaskService.create({
|
||||||
|
...createMessagePushingTaskDto,
|
||||||
|
ownerId: userId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
taskId: messagePushingTask._id.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
description: '推送任务列表',
|
||||||
|
status: 200,
|
||||||
|
type: MessagePushingTaskListSucceedResponse,
|
||||||
|
})
|
||||||
|
@Get('')
|
||||||
|
async findAll(
|
||||||
|
@Request()
|
||||||
|
req,
|
||||||
|
@Query() query: QueryMessagePushingTaskListDto,
|
||||||
|
) {
|
||||||
|
const userId = req.user._id;
|
||||||
|
if (!query?.surveyId && !query?.triggerHook && !userId) {
|
||||||
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
const list = await this.messagePushingTaskService.findAll({
|
||||||
|
surveyId: query.surveyId,
|
||||||
|
hook: query.triggerHook,
|
||||||
|
ownerId: userId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: list,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
description: '查询到的推送任务',
|
||||||
|
status: 200,
|
||||||
|
type: MessagePushingTaskSucceedResponseDto,
|
||||||
|
})
|
||||||
|
@Get(':id')
|
||||||
|
async findOne(@Request() req, @Param('id') id: string) {
|
||||||
|
const userId = req.user._id;
|
||||||
|
const task = await this.messagePushingTaskService.findOne({
|
||||||
|
ownerId: userId,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: task,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
description: '更新结果',
|
||||||
|
status: 200,
|
||||||
|
type: MessagePushingTaskSucceedResponseDto,
|
||||||
|
})
|
||||||
|
@Put(':id')
|
||||||
|
async update(
|
||||||
|
@Request() req,
|
||||||
|
@Param('id') id: string,
|
||||||
|
@Body() updateMessagePushingTaskDto: UpdateMessagePushingTaskDto,
|
||||||
|
) {
|
||||||
|
const userId = req.user._id;
|
||||||
|
const newTask = await this.messagePushingTaskService.update({
|
||||||
|
id,
|
||||||
|
ownerId: userId,
|
||||||
|
updateData: updateMessagePushingTaskDto,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: newTask,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
description: '删除结果',
|
||||||
|
status: 200,
|
||||||
|
type: CodeDto,
|
||||||
|
})
|
||||||
|
@Delete(':id')
|
||||||
|
async remove(@Request() req, @Param('id') id: string) {
|
||||||
|
const userId = req.user._id;
|
||||||
|
const res = await this.messagePushingTaskService.remove({
|
||||||
|
ownerId: userId,
|
||||||
|
id,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: res.modifiedCount === 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@ApiResponse({
|
||||||
|
description: '给任务绑定新问卷',
|
||||||
|
status: 200,
|
||||||
|
})
|
||||||
|
@Post(':taskId/surveys/:surveyId')
|
||||||
|
async surveyAuthorizeTask(
|
||||||
|
@Request() req,
|
||||||
|
@Param('taskId') taskId: string,
|
||||||
|
@Param('surveyId') surveyId: string,
|
||||||
|
) {
|
||||||
|
const userId = req.user._id;
|
||||||
|
const res = await this.messagePushingTaskService.surveyAuthorizeTask({
|
||||||
|
taskId,
|
||||||
|
surveyId,
|
||||||
|
ownerId: userId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: res.modifiedCount === 1,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -16,12 +16,16 @@ import { ContentSecurityService } from '../services/contentSecurity.service';
|
|||||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||||
|
|
||||||
import BannerData from '../template/banner/index.json';
|
import BannerData from '../template/banner/index.json';
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
import { Authtication } from 'src/guards/authtication';
|
||||||
import { HISTORY_TYPE } from 'src/enums';
|
import { HISTORY_TYPE } from 'src/enums';
|
||||||
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 { Logger } from 'src/logger';
|
||||||
|
|
||||||
|
@ApiTags('survey')
|
||||||
@Controller('/api/survey')
|
@Controller('/api/survey')
|
||||||
export class SurveyController {
|
export class SurveyController {
|
||||||
constructor(
|
constructor(
|
||||||
@ -30,6 +34,7 @@ export class SurveyController {
|
|||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly contentSecurityService: ContentSecurityService,
|
private readonly contentSecurityService: ContentSecurityService,
|
||||||
private readonly surveyHistoryService: SurveyHistoryService,
|
private readonly surveyHistoryService: SurveyHistoryService,
|
||||||
|
private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Get('/getBannerData')
|
@Get('/getBannerData')
|
||||||
@ -68,6 +73,9 @@ export class SurveyController {
|
|||||||
}),
|
}),
|
||||||
}).validateAsync(reqBody);
|
}).validateAsync(reqBody);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.logger.error(`createSurvey_parameter error: ${error.message}`, {
|
||||||
|
req,
|
||||||
|
});
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -11,8 +11,10 @@ import { SurveyHistoryService } from '../services/surveyHistory.service';
|
|||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
import { Authtication } from 'src/guards/authtication';
|
||||||
|
|
||||||
|
@ApiTags('survey')
|
||||||
@Controller('/api/surveyHisotry')
|
@Controller('/api/surveyHisotry')
|
||||||
export class SurveyHistoryController {
|
export class SurveyHistoryController {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -10,17 +10,23 @@ import {
|
|||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import moment from 'moment';
|
import moment from 'moment';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { getFilter, getOrder } from 'src/utils/surveyUtil';
|
import { getFilter, getOrder } from 'src/utils/surveyUtil';
|
||||||
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 { Authtication } from 'src/guards/authtication';
|
import { Authtication } from 'src/guards/authtication';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
|
||||||
|
@ApiTags('survey')
|
||||||
@Controller('/api/survey')
|
@Controller('/api/survey')
|
||||||
export class SurveyMetaController {
|
export class SurveyMetaController {
|
||||||
constructor(private readonly surveyMetaService: SurveyMetaService) {}
|
constructor(
|
||||||
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
@UseGuards(Authtication)
|
||||||
@Post('/updateMeta')
|
@Post('/updateMeta')
|
||||||
@ -34,6 +40,9 @@ export class SurveyMetaController {
|
|||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
}).validateAsync(reqBody, { allowUnknown: true });
|
}).validateAsync(reqBody, { allowUnknown: true });
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
|
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
||||||
|
req,
|
||||||
|
});
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
import { Controller, Get, Res } from '@nestjs/common';
|
import { Controller, Get, Res } from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
@ApiTags('ui')
|
||||||
@Controller()
|
@Controller()
|
||||||
export class SurveyUIController {
|
export class SurveyUIController {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
@ -0,0 +1,22 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
export class CreateMessagePushingTaskDto {
|
||||||
|
@ApiProperty({ description: '任务名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务类型' })
|
||||||
|
type: MESSAGE_PUSHING_TYPE;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '推送的http链接' })
|
||||||
|
pushAddress: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '触发时机' })
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '包含问卷id' })
|
||||||
|
surveys?: string[];
|
||||||
|
}
|
62
server/src/modules/survey/dto/messagePushingTask.dto.ts
Normal file
62
server/src/modules/survey/dto/messagePushingTask.dto.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
export class MessagePushingTaskDto {
|
||||||
|
@ApiProperty({ description: '任务id' })
|
||||||
|
_id: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务名称' })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务类型' })
|
||||||
|
type: MESSAGE_PUSHING_TYPE;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '推送的http链接' })
|
||||||
|
pushAddress: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '触发时机' })
|
||||||
|
triggerHook: MESSAGE_PUSHING_HOOK;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '包含问卷id' })
|
||||||
|
surveys: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '所有者' })
|
||||||
|
owner: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务状态', required: false })
|
||||||
|
curStatus?: {
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
export class CodeDto {
|
||||||
|
@ApiProperty({ description: '状态码', default: 200 })
|
||||||
|
code: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class TaskIdDto {
|
||||||
|
@ApiProperty({ description: '任务id' })
|
||||||
|
taskId: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MessagePushingTaskSucceedResponseDto {
|
||||||
|
@ApiProperty({ description: '状态码', default: 200 })
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务详情' })
|
||||||
|
data: MessagePushingTaskDto;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class MessagePushingTaskListSucceedResponse {
|
||||||
|
@ApiProperty({ description: '状态码', default: 200 })
|
||||||
|
code: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务详情' })
|
||||||
|
data: [MessagePushingTaskDto];
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
export class QueryMessagePushingTaskListDto {
|
||||||
|
@ApiProperty({ description: '问卷id', required: false })
|
||||||
|
surveyId?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: 'hook名称', required: false })
|
||||||
|
triggerHook?: MESSAGE_PUSHING_HOOK;
|
||||||
|
}
|
@ -0,0 +1,29 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
|
||||||
|
export class UpdateMessagePushingTaskDto {
|
||||||
|
@ApiProperty({ description: '任务名称', required: false })
|
||||||
|
name?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务类型' })
|
||||||
|
type?: MESSAGE_PUSHING_TYPE;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '推送的http链接' })
|
||||||
|
pushAddress?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '触发时机' })
|
||||||
|
triggerHook?: MESSAGE_PUSHING_HOOK;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '绑定的问卷id', required: false })
|
||||||
|
surveys?: string[];
|
||||||
|
|
||||||
|
@ApiProperty({ description: '任务状态', required: false })
|
||||||
|
curStatus?: {
|
||||||
|
status: RECORD_STATUS;
|
||||||
|
date: number;
|
||||||
|
};
|
||||||
|
}
|
149
server/src/modules/survey/services/messagePushingTask.service.ts
Normal file
149
server/src/modules/survey/services/messagePushingTask.service.ts
Normal file
@ -0,0 +1,149 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
|
import { MESSAGE_PUSHING_HOOK } from 'src/enums/messagePushing';
|
||||||
|
import { CreateMessagePushingTaskDto } from '../dto/createMessagePushingTask.dto';
|
||||||
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessagePushingTaskService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(MessagePushingTask)
|
||||||
|
private readonly messagePushingTaskRepository: MongoRepository<MessagePushingTask>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(
|
||||||
|
createMessagePushingTaskDto: CreateMessagePushingTaskDto & {
|
||||||
|
ownerId: string;
|
||||||
|
},
|
||||||
|
): Promise<MessagePushingTask> {
|
||||||
|
const createdTask = this.messagePushingTaskRepository.create(
|
||||||
|
createMessagePushingTaskDto,
|
||||||
|
);
|
||||||
|
createdTask.creatorId = createdTask.ownerId;
|
||||||
|
if (!createdTask.surveys) {
|
||||||
|
createdTask.surveys = [];
|
||||||
|
}
|
||||||
|
return await this.messagePushingTaskRepository.save(createdTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAll({
|
||||||
|
surveyId,
|
||||||
|
hook,
|
||||||
|
ownerId,
|
||||||
|
}: {
|
||||||
|
surveyId?: string;
|
||||||
|
hook?: MESSAGE_PUSHING_HOOK;
|
||||||
|
ownerId?: string;
|
||||||
|
}): Promise<MessagePushingTask[]> {
|
||||||
|
const where: Record<string, any> = {
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
if (surveyId) {
|
||||||
|
where.surveys = {
|
||||||
|
$all: [surveyId],
|
||||||
|
};
|
||||||
|
}
|
||||||
|
if (hook) {
|
||||||
|
where.triggerHook = hook;
|
||||||
|
}
|
||||||
|
if (ownerId) {
|
||||||
|
where.ownerId = ownerId;
|
||||||
|
}
|
||||||
|
return await this.messagePushingTaskRepository.find({
|
||||||
|
where,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne({
|
||||||
|
id,
|
||||||
|
ownerId,
|
||||||
|
}: {
|
||||||
|
id: string;
|
||||||
|
ownerId: string;
|
||||||
|
}): Promise<MessagePushingTask> {
|
||||||
|
return await this.messagePushingTaskRepository.findOne({
|
||||||
|
where: {
|
||||||
|
ownerId,
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async update({
|
||||||
|
ownerId,
|
||||||
|
id,
|
||||||
|
updateData,
|
||||||
|
}: {
|
||||||
|
ownerId: string;
|
||||||
|
id: string;
|
||||||
|
updateData: UpdateMessagePushingTaskDto;
|
||||||
|
}): Promise<MessagePushingTask> {
|
||||||
|
const existingTask = await this.messagePushingTaskRepository.findOne({
|
||||||
|
where: {
|
||||||
|
ownerId,
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
if (!existingTask) {
|
||||||
|
throw new Error(`Message pushing task with id ${id} not found.`);
|
||||||
|
}
|
||||||
|
const updatedTask = Object.assign(existingTask, updateData);
|
||||||
|
return await this.messagePushingTaskRepository.save(updatedTask);
|
||||||
|
}
|
||||||
|
|
||||||
|
async remove({ id, ownerId }: { id: string; ownerId: string }) {
|
||||||
|
const curStatus = {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: Date.now(),
|
||||||
|
};
|
||||||
|
return this.messagePushingTaskRepository.updateOne(
|
||||||
|
{
|
||||||
|
ownerId,
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
curStatus,
|
||||||
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: curStatus as never,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async surveyAuthorizeTask({
|
||||||
|
taskId,
|
||||||
|
surveyId,
|
||||||
|
ownerId,
|
||||||
|
}: {
|
||||||
|
taskId: string;
|
||||||
|
surveyId: string;
|
||||||
|
ownerId: string;
|
||||||
|
}) {
|
||||||
|
return this.messagePushingTaskRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(taskId),
|
||||||
|
surveys: { $nin: [surveyId] },
|
||||||
|
ownerId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$push: {
|
||||||
|
surveys: surveyId as never,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
@ -47,6 +47,7 @@ export class SurveyHistoryService {
|
|||||||
order: {
|
order: {
|
||||||
createDate: -1,
|
createDate: -1,
|
||||||
},
|
},
|
||||||
|
select: ['createDate', 'operator', 'type', '_id'],
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
|
|||||||
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
import { ConfigModule } from '@nestjs/config';
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
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';
|
||||||
|
|
||||||
@ -10,18 +12,21 @@ import { SurveyController } from './controllers/survey.controller';
|
|||||||
import { SurveyHistoryController } from './controllers/surveyHistory.controller';
|
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 { MessagePushingTaskController } from './controllers/messagePushingTask.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';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
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 { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
|
|
||||||
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 { MessagePushingTaskService } from './services/messagePushingTask.service';
|
||||||
|
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
|
|
||||||
@ -33,6 +38,7 @@ import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider
|
|||||||
SurveyHistory,
|
SurveyHistory,
|
||||||
SurveyResponse,
|
SurveyResponse,
|
||||||
Word,
|
Word,
|
||||||
|
MessagePushingTask,
|
||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
SurveyResponseModule,
|
SurveyResponseModule,
|
||||||
@ -44,6 +50,7 @@ import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider
|
|||||||
SurveyHistoryController,
|
SurveyHistoryController,
|
||||||
SurveyMetaController,
|
SurveyMetaController,
|
||||||
SurveyUIController,
|
SurveyUIController,
|
||||||
|
MessagePushingTaskController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DataStatisticService,
|
DataStatisticService,
|
||||||
@ -52,6 +59,8 @@ import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider
|
|||||||
SurveyMetaService,
|
SurveyMetaService,
|
||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
ContentSecurityService,
|
ContentSecurityService,
|
||||||
|
MessagePushingTaskService,
|
||||||
|
LoggerProvider,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class SurveyModule {}
|
export class SurveyModule {}
|
||||||
|
@ -113,7 +113,6 @@
|
|||||||
"type": "radio-star",
|
"type": "radio-star",
|
||||||
"title": "标题2",
|
"title": "标题2",
|
||||||
"answer": "",
|
"answer": "",
|
||||||
"options": [],
|
|
||||||
"textRange": {
|
"textRange": {
|
||||||
"min": {
|
"min": {
|
||||||
"placeholder": "0",
|
"placeholder": "0",
|
||||||
|
@ -27,24 +27,6 @@
|
|||||||
"checked": false,
|
"checked": false,
|
||||||
"minNum": "",
|
"minNum": "",
|
||||||
"maxNum": "",
|
"maxNum": "",
|
||||||
"options": [
|
|
||||||
{
|
|
||||||
"text": "选项1",
|
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
|
||||||
"mustOthers": false,
|
|
||||||
"othersKey": "",
|
|
||||||
"placeholderDesc": ""
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"text": "选项2",
|
|
||||||
"imageUrl": "",
|
|
||||||
"others": false,
|
|
||||||
"mustOthers": false,
|
|
||||||
"othersKey": "",
|
|
||||||
"placeholderDesc": ""
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"star": 5,
|
"star": 5,
|
||||||
"nps": {
|
"nps": {
|
||||||
"leftText": "极不满意",
|
"leftText": "极不满意",
|
||||||
|
@ -0,0 +1,110 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { MessagePushingLogService } from '../services/messagePushingLog.service';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { MessagePushingLog } from 'src/models/messagePushingLog.entity';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
describe('MessagePushingLogService', () => {
|
||||||
|
let service: MessagePushingLogService;
|
||||||
|
let repository: MongoRepository<MessagePushingLog>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
MessagePushingLogService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(MessagePushingLog),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<MessagePushingLogService>(MessagePushingLogService);
|
||||||
|
repository = module.get<MongoRepository<MessagePushingLog>>(
|
||||||
|
getRepositoryToken(MessagePushingLog),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(service).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('createPushingLog', () => {
|
||||||
|
it('should create a message pushing log', async () => {
|
||||||
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
const request = { reqKey: 'value' };
|
||||||
|
const response = { resKey: 'value' };
|
||||||
|
const status = 200;
|
||||||
|
|
||||||
|
const createdLog = new MessagePushingLog();
|
||||||
|
createdLog.taskId = taskId;
|
||||||
|
createdLog.request = request;
|
||||||
|
createdLog.response = response;
|
||||||
|
createdLog.status = status;
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'create').mockReturnValue(createdLog);
|
||||||
|
jest.spyOn(repository, 'save').mockResolvedValue(createdLog);
|
||||||
|
|
||||||
|
const result = await service.createPushingLog({
|
||||||
|
taskId,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(result).toEqual(createdLog);
|
||||||
|
expect(repository.create).toHaveBeenCalledWith({
|
||||||
|
taskId,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
expect(repository.save).toHaveBeenCalledWith(createdLog);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAllByTaskId', () => {
|
||||||
|
it('should find all message pushing logs by task id', async () => {
|
||||||
|
const taskId = '65afc62904d5db18534c0f78';
|
||||||
|
const logs = [{ taskId, request: {}, response: {}, status: 200 }];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'find')
|
||||||
|
.mockResolvedValue(logs as MessagePushingLog[]);
|
||||||
|
|
||||||
|
const result = await service.findAllByTaskId(taskId);
|
||||||
|
|
||||||
|
expect(result).toEqual(logs);
|
||||||
|
expect(repository.find).toHaveBeenCalledWith({ where: { taskId } });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', () => {
|
||||||
|
it('should find one message pushing log by id', async () => {
|
||||||
|
const logId = '65af380475b64545e5277dd9';
|
||||||
|
const log = {
|
||||||
|
_id: new ObjectId(logId),
|
||||||
|
taskId: '65afc62904d5db18534c0f78',
|
||||||
|
request: {},
|
||||||
|
response: {},
|
||||||
|
status: 200,
|
||||||
|
};
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'findOne')
|
||||||
|
.mockResolvedValue(log as MessagePushingLog);
|
||||||
|
|
||||||
|
const result = await service.findOne(logId);
|
||||||
|
|
||||||
|
expect(result).toEqual(log);
|
||||||
|
expect(repository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: { _id: new ObjectId(logId) },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,17 +1,27 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { cloneDeep } from 'lodash';
|
||||||
|
|
||||||
|
import { mockResponseSchema } from './mockResponseSchema';
|
||||||
|
|
||||||
import { SurveyResponseController } from '../controllers/surveyResponse.controller';
|
import { SurveyResponseController } from '../controllers/surveyResponse.controller';
|
||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { CounterService } from '../services/counter.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 { mockResponseSchema } from './mockResponseSchema';
|
import { MessagePushingTaskService } from '../../survey/services/messagePushingTask.service';
|
||||||
|
import { MessagePushingLogService } from '../services/messagePushingLog.service';
|
||||||
|
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
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 { cloneDeep } from 'lodash';
|
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
|
|
||||||
|
import { MESSAGE_PUSHING_TYPE } from 'src/enums/messagePushing';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
|
|
||||||
const mockDecryptErrorBody = {
|
const mockDecryptErrorBody = {
|
||||||
surveyPath: 'EBzdmnSp',
|
surveyPath: 'EBzdmnSp',
|
||||||
@ -65,9 +75,10 @@ const mockClientEncryptInfo = {
|
|||||||
describe('SurveyResponseController', () => {
|
describe('SurveyResponseController', () => {
|
||||||
let controller: SurveyResponseController;
|
let controller: SurveyResponseController;
|
||||||
let responseSchemaService: ResponseSchemaService;
|
let responseSchemaService: ResponseSchemaService;
|
||||||
// let counterService: CounterService;
|
|
||||||
let surveyResponseService: SurveyResponseService;
|
let surveyResponseService: SurveyResponseService;
|
||||||
let clientEncryptService: ClientEncryptService;
|
let clientEncryptService: ClientEncryptService;
|
||||||
|
let messagePushingTaskService: MessagePushingTaskService;
|
||||||
|
let messagePushingLogService: MessagePushingLogService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -102,6 +113,18 @@ describe('SurveyResponseController', () => {
|
|||||||
.mockResolvedValue(mockClientEncryptInfo),
|
.mockResolvedValue(mockClientEncryptInfo),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: MessagePushingTaskService,
|
||||||
|
useValue: {
|
||||||
|
findAll: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: MessagePushingLogService,
|
||||||
|
useValue: {
|
||||||
|
createPushingLog: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
@ -110,13 +133,20 @@ describe('SurveyResponseController', () => {
|
|||||||
responseSchemaService = module.get<ResponseSchemaService>(
|
responseSchemaService = module.get<ResponseSchemaService>(
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
);
|
);
|
||||||
// counterService = module.get<CounterService>(CounterService);
|
|
||||||
surveyResponseService = module.get<SurveyResponseService>(
|
surveyResponseService = module.get<SurveyResponseService>(
|
||||||
SurveyResponseService,
|
SurveyResponseService,
|
||||||
);
|
);
|
||||||
clientEncryptService =
|
clientEncryptService =
|
||||||
module.get<ClientEncryptService>(ClientEncryptService);
|
module.get<ClientEncryptService>(ClientEncryptService);
|
||||||
|
|
||||||
|
messagePushingTaskService = module.get<MessagePushingTaskService>(
|
||||||
|
MessagePushingTaskService,
|
||||||
|
);
|
||||||
|
|
||||||
|
messagePushingLogService = module.get<MessagePushingLogService>(
|
||||||
|
MessagePushingLogService,
|
||||||
|
);
|
||||||
|
|
||||||
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||||
XiaojuSurveyPluginManager,
|
XiaojuSurveyPluginManager,
|
||||||
);
|
);
|
||||||
@ -141,10 +171,52 @@ describe('SurveyResponseController', () => {
|
|||||||
.mockResolvedValueOnce(0);
|
.mockResolvedValueOnce(0);
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyResponseService, 'createSurveyResponse')
|
.spyOn(surveyResponseService, 'createSurveyResponse')
|
||||||
.mockResolvedValueOnce(undefined);
|
.mockResolvedValueOnce({
|
||||||
|
_id: new ObjectId('65fc2dd77f4520858046e129'),
|
||||||
|
clientTime: 1711025112552,
|
||||||
|
createDate: 1711025113146,
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.NEW,
|
||||||
|
date: 1711025113146,
|
||||||
|
},
|
||||||
|
difTime: 30518,
|
||||||
|
data: {
|
||||||
|
data458: '15000000000',
|
||||||
|
data515: '115019',
|
||||||
|
data450: '450111000000000000',
|
||||||
|
data405: '浙江省杭州市西湖区xxx',
|
||||||
|
data770: '123456@qq.com',
|
||||||
|
},
|
||||||
|
optionTextAndId: {
|
||||||
|
data515: [
|
||||||
|
{
|
||||||
|
hash: '115019',
|
||||||
|
text: '<p>男</p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hash: '115020',
|
||||||
|
text: '<p>女</p>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
|
statusList: [
|
||||||
|
{
|
||||||
|
status: RECORD_STATUS.NEW,
|
||||||
|
date: 1711025113146,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
surveyPath: 'EBzdmnSp',
|
||||||
|
updateDate: 1711025113146,
|
||||||
|
secretKeys: [],
|
||||||
|
} as unknown as SurveyResponse);
|
||||||
jest
|
jest
|
||||||
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
||||||
.mockResolvedValueOnce(undefined);
|
.mockResolvedValueOnce(undefined);
|
||||||
|
jest
|
||||||
|
.spyOn(controller, 'sendSurveyResponseMessage')
|
||||||
|
.mockReturnValueOnce(undefined);
|
||||||
|
|
||||||
const result = await controller.createResponse(reqBody);
|
const result = await controller.createResponse(reqBody);
|
||||||
|
|
||||||
@ -166,7 +238,7 @@ describe('SurveyResponseController', () => {
|
|||||||
},
|
},
|
||||||
clientTime: reqBody.clientTime,
|
clientTime: reqBody.clientTime,
|
||||||
difTime: reqBody.difTime,
|
difTime: reqBody.difTime,
|
||||||
surveyId: mockResponseSchema.pageId, // mock response schema 的 pageId
|
surveyId: mockResponseSchema.pageId,
|
||||||
optionTextAndId: {
|
optionTextAndId: {
|
||||||
data515: [
|
data515: [
|
||||||
{
|
{
|
||||||
@ -180,6 +252,7 @@ describe('SurveyResponseController', () => {
|
|||||||
],
|
],
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(clientEncryptService.deleteEncryptInfo).toHaveBeenCalledWith(
|
expect(clientEncryptService.deleteEncryptInfo).toHaveBeenCalledWith(
|
||||||
reqBody.sessionId,
|
reqBody.sessionId,
|
||||||
);
|
);
|
||||||
@ -247,4 +320,101 @@ describe('SurveyResponseController', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('sendSurveyResponseMessage', () => {
|
||||||
|
it('should send survey response message', async () => {
|
||||||
|
const sendData = {
|
||||||
|
surveyId: '65f29f3192862d6a9067ad1c',
|
||||||
|
surveyPath: 'EBzdmnSp',
|
||||||
|
surveyResponseId: '65fc2dd77f4520858046e129',
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
questionId: 'data458',
|
||||||
|
title: '<p>您的手机号</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['15000000000'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data515',
|
||||||
|
title: '<p>您的性别</p>',
|
||||||
|
valueType: 'option',
|
||||||
|
alias: '',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
alias: '',
|
||||||
|
id: '115019',
|
||||||
|
text: '<p>男</p>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data450',
|
||||||
|
title: '<p>身份证</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['450111000000000000'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data405',
|
||||||
|
title: '<p>地址</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['浙江省杭州市西湖区xxx'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data770',
|
||||||
|
title: '<p>邮箱</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['123456@qq.com'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
const mockTasks = [
|
||||||
|
{
|
||||||
|
_id: new ObjectId('65fc31dbfd09a5d0619c3b74'),
|
||||||
|
name: 'Task 1',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: 'success_url',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
_id: new ObjectId('65fc31dbfd09a5d0619c3b75'),
|
||||||
|
name: 'Task 2',
|
||||||
|
type: MESSAGE_PUSHING_TYPE.HTTP,
|
||||||
|
pushAddress: 'fail_url',
|
||||||
|
},
|
||||||
|
] as MessagePushingTask[];
|
||||||
|
jest
|
||||||
|
.spyOn(messagePushingTaskService, 'findAll')
|
||||||
|
.mockReturnValue(Promise.resolve(mockTasks));
|
||||||
|
|
||||||
|
const mockFetch = jest.fn().mockImplementation((url) => {
|
||||||
|
if (url === 'success_url') {
|
||||||
|
return {
|
||||||
|
json: () => {
|
||||||
|
return Promise.resolve({ code: 200, msg: '提交成功' });
|
||||||
|
},
|
||||||
|
status: 200,
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
data: 'failed',
|
||||||
|
status: 501,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
jest.mock('node-fetch', () => jest.fn().mockImplementation(mockFetch));
|
||||||
|
|
||||||
|
await controller.sendSurveyResponseMessage({
|
||||||
|
sendData,
|
||||||
|
surveyId: mockResponseSchema.pageId,
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(messagePushingTaskService.findAll).toHaveBeenCalled();
|
||||||
|
|
||||||
|
expect(messagePushingLogService.createPushingLog).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@ -21,10 +21,9 @@ describe('SurveyResponseUIController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
it('should render the survey response with the correct path', () => {
|
it('should render the survey response with the correct path', () => {
|
||||||
const surveyPath = 'some-survey-path';
|
|
||||||
const expectedFilePath = join(process.cwd(), 'public', 'render.html');
|
const expectedFilePath = join(process.cwd(), 'public', 'render.html');
|
||||||
|
|
||||||
controller.render(surveyPath, res);
|
controller.render(res);
|
||||||
|
|
||||||
expect(res.sendFile).toHaveBeenCalledWith(expectedFilePath);
|
expect(res.sendFile).toHaveBeenCalledWith(expectedFilePath);
|
||||||
});
|
});
|
||||||
|
@ -3,7 +3,9 @@ import { ConfigService } from '@nestjs/config';
|
|||||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||||
import * as forge from 'node-forge';
|
import * as forge from 'node-forge';
|
||||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/clientEncrypt')
|
@Controller('/api/clientEncrypt')
|
||||||
export class ClientEncryptController {
|
export class ClientEncryptController {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -2,7 +2,9 @@ import { Controller, Get, HttpCode, Query } from '@nestjs/common';
|
|||||||
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 { CounterService } from '../services/counter.service';
|
import { CounterService } from '../services/counter.service';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/counter')
|
@Controller('/api/counter')
|
||||||
export class CounterController {
|
export class CounterController {
|
||||||
constructor(private readonly counterService: CounterService) {}
|
constructor(private readonly counterService: CounterService) {}
|
||||||
|
@ -3,7 +3,9 @@ import { ResponseSchemaService } from '../services/responseScheme.service';
|
|||||||
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 { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/responseSchema')
|
@Controller('/api/responseSchema')
|
||||||
export class ResponseSchemaController {
|
export class ResponseSchemaController {
|
||||||
constructor(private readonly responseSchemaService: ResponseSchemaService) {}
|
constructor(private readonly responseSchemaService: ResponseSchemaService) {}
|
||||||
|
@ -2,16 +2,28 @@ 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';
|
||||||
import * as Joi from 'joi';
|
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import {
|
||||||
|
MESSAGE_PUSHING_HOOK,
|
||||||
|
MESSAGE_PUSHING_TYPE,
|
||||||
|
} from 'src/enums/messagePushing';
|
||||||
|
import { getPushingData } from 'src/utils/messagePushing';
|
||||||
|
|
||||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||||
import { CounterService } from '../services/counter.service';
|
import { CounterService } from '../services/counter.service';
|
||||||
import moment from 'moment';
|
|
||||||
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 { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
import { MessagePushingTaskService } from '../../survey/services/messagePushingTask.service';
|
||||||
import * as forge from 'node-forge';
|
import { MessagePushingLogService } from '../services/messagePushingLog.service';
|
||||||
|
|
||||||
|
import moment from 'moment';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import * as forge from 'node-forge';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import fetch from 'node-fetch';
|
||||||
|
|
||||||
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/surveyResponse')
|
@Controller('/api/surveyResponse')
|
||||||
export class SurveyResponseController {
|
export class SurveyResponseController {
|
||||||
constructor(
|
constructor(
|
||||||
@ -19,6 +31,8 @@ export class SurveyResponseController {
|
|||||||
private readonly counterService: CounterService,
|
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 messagePushingLogService: MessagePushingLogService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/createResponse')
|
@Post('/createResponse')
|
||||||
@ -174,13 +188,27 @@ export class SurveyResponseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 入库
|
// 入库
|
||||||
await this.surveyResponseService.createSurveyResponse({
|
const surveyResponse =
|
||||||
surveyPath: validationResult.surveyPath,
|
await this.surveyResponseService.createSurveyResponse({
|
||||||
data: decryptedData,
|
surveyPath: validationResult.surveyPath,
|
||||||
clientTime,
|
data: decryptedData,
|
||||||
difTime,
|
clientTime,
|
||||||
|
difTime,
|
||||||
|
surveyId: responseSchema.pageId,
|
||||||
|
optionTextAndId,
|
||||||
|
});
|
||||||
|
|
||||||
|
const sendData = getPushingData({
|
||||||
|
surveyResponse,
|
||||||
|
questionList: responseSchema?.code?.dataConf?.dataList || [],
|
||||||
|
surveyId: responseSchema.pageId,
|
||||||
|
surveyPath: responseSchema.surveyPath,
|
||||||
|
});
|
||||||
|
|
||||||
|
// 数据异步推送
|
||||||
|
this.sendSurveyResponseMessage({
|
||||||
|
sendData,
|
||||||
surveyId: responseSchema.pageId,
|
surveyId: responseSchema.pageId,
|
||||||
optionTextAndId,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
// 入库成功后,要把密钥删掉,防止被重复使用
|
// 入库成功后,要把密钥删掉,防止被重复使用
|
||||||
@ -191,4 +219,53 @@ export class SurveyResponseController {
|
|||||||
msg: '提交成功',
|
msg: '提交成功',
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async sendSurveyResponseMessage({ sendData, surveyId }) {
|
||||||
|
try {
|
||||||
|
// 数据推送
|
||||||
|
const messagePushingTasks = await this.messagePushingTaskService.findAll({
|
||||||
|
surveyId,
|
||||||
|
hook: MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (
|
||||||
|
Array.isArray(messagePushingTasks) &&
|
||||||
|
messagePushingTasks.length > 0
|
||||||
|
) {
|
||||||
|
for (const task of messagePushingTasks) {
|
||||||
|
switch (task.type) {
|
||||||
|
case MESSAGE_PUSHING_TYPE.HTTP: {
|
||||||
|
try {
|
||||||
|
const res = await fetch(task.pushAddress, {
|
||||||
|
method: 'POST',
|
||||||
|
headers: {
|
||||||
|
Accept: 'application/json, */*',
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
},
|
||||||
|
body: JSON.stringify(sendData),
|
||||||
|
});
|
||||||
|
const response = await res.json();
|
||||||
|
await this.messagePushingLogService.createPushingLog({
|
||||||
|
taskId: task._id.toString(),
|
||||||
|
request: sendData,
|
||||||
|
response: response,
|
||||||
|
status: res.status,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
await this.messagePushingLogService.createPushingLog({
|
||||||
|
taskId: task._id.toString(),
|
||||||
|
request: sendData,
|
||||||
|
response: error.data || error.message,
|
||||||
|
status: error.status || 500,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,13 +1,14 @@
|
|||||||
import { Controller, Get, Param, Res } from '@nestjs/common';
|
import { Controller, Get, Res } from '@nestjs/common';
|
||||||
import { Response } from 'express';
|
import { Response } from 'express';
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
@ApiTags('ui')
|
||||||
@Controller()
|
@Controller()
|
||||||
export class SurveyResponseUIController {
|
export class SurveyResponseUIController {
|
||||||
constructor() {}
|
constructor() {}
|
||||||
|
|
||||||
@Get('/render/:surveyPath')
|
@Get('/render/:path*')
|
||||||
render(@Param('surveyPath') surveyPath: string, @Res() res: Response) {
|
render(@Res() res: Response) {
|
||||||
res.sendFile(join(process.cwd(), 'public', 'render.html'));
|
res.sendFile(join(process.cwd(), 'public', 'render.html'));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,44 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { MessagePushingLog } from 'src/models/messagePushingLog.entity';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class MessagePushingLogService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(MessagePushingLog)
|
||||||
|
private readonly messagePushingLogRepository: MongoRepository<MessagePushingLog>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async createPushingLog({
|
||||||
|
taskId,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
status,
|
||||||
|
}): Promise<MessagePushingLog> {
|
||||||
|
const createdLog = this.messagePushingLogRepository.create({
|
||||||
|
taskId,
|
||||||
|
request,
|
||||||
|
response,
|
||||||
|
status,
|
||||||
|
});
|
||||||
|
return await this.messagePushingLogRepository.save(createdLog);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllByTaskId(taskId: string): Promise<MessagePushingLog[]> {
|
||||||
|
return await this.messagePushingLogRepository.find({
|
||||||
|
where: {
|
||||||
|
taskId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne(id: string): Promise<MessagePushingLog> {
|
||||||
|
return await this.messagePushingLogRepository.findOne({
|
||||||
|
where: {
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -28,7 +28,10 @@ export class SurveyResponseService {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// 提交问卷
|
// 提交问卷
|
||||||
return await this.surveyResponseRepository.save(newSubmitData);
|
const res = await this.surveyResponseRepository.save(newSubmitData);
|
||||||
|
// res是加密后的数据,需要手动调用loaded才会触发解密
|
||||||
|
res.onDataLoaded();
|
||||||
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
async getSurveyResponseTotalByPath(surveyPath: string) {
|
async getSurveyResponseTotalByPath(surveyPath: string) {
|
||||||
|
@ -4,11 +4,15 @@ 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 { MessagePushingTaskService } from '../survey/services/messagePushingTask.service';
|
||||||
|
import { MessagePushingLogService } from './services/messagePushingLog.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 { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
|
import { MessagePushingLog } from 'src/models/messagePushingLog.entity';
|
||||||
|
|
||||||
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
||||||
import { CounterController } from './controllers/counter.controller';
|
import { CounterController } from './controllers/counter.controller';
|
||||||
@ -26,6 +30,8 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
Counter,
|
Counter,
|
||||||
SurveyResponse,
|
SurveyResponse,
|
||||||
ClientEncrypt,
|
ClientEncrypt,
|
||||||
|
MessagePushingTask,
|
||||||
|
MessagePushingLog,
|
||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
],
|
],
|
||||||
@ -41,6 +47,8 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
SurveyResponseService,
|
SurveyResponseService,
|
||||||
CounterService,
|
CounterService,
|
||||||
ClientEncryptService,
|
ClientEncryptService,
|
||||||
|
MessagePushingTaskService,
|
||||||
|
MessagePushingLogService,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
|
155
server/src/utils/messagePushing.spec.ts
Normal file
155
server/src/utils/messagePushing.spec.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { getPushingData } from './messagePushing';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
describe('getPushingData', () => {
|
||||||
|
it('should combine survey response data with response schema correctly', () => {
|
||||||
|
const surveyResponse = {
|
||||||
|
_id: new ObjectId('65fc2dd77f4520858046e129'),
|
||||||
|
clientTime: 1711025112552,
|
||||||
|
createDate: 1711025113146,
|
||||||
|
curStatus: {
|
||||||
|
status: RECORD_STATUS.NEW,
|
||||||
|
date: 1711025113146,
|
||||||
|
},
|
||||||
|
difTime: 30518,
|
||||||
|
data: {
|
||||||
|
data458: '15000000000',
|
||||||
|
data515: '115019',
|
||||||
|
data450: '450111000000000000',
|
||||||
|
data405: '浙江省杭州市西湖区xxx',
|
||||||
|
data770: '123456@qq.com',
|
||||||
|
},
|
||||||
|
optionTextAndId: {
|
||||||
|
data515: [
|
||||||
|
{
|
||||||
|
hash: '115019',
|
||||||
|
text: '<p>男</p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
hash: '115020',
|
||||||
|
text: '<p>女</p>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
|
statusList: [
|
||||||
|
{
|
||||||
|
status: RECORD_STATUS.NEW,
|
||||||
|
date: 1711025113146,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
|
||||||
|
surveyPath: 'EBzdmnSp',
|
||||||
|
updateDate: 1711025113146,
|
||||||
|
secretKeys: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
// Mock response schema data
|
||||||
|
const responseSchema = {
|
||||||
|
_id: new ObjectId('65f29f8892862d6a9067ad25'),
|
||||||
|
pageId: '65f29f3192862d6a9067ad1c',
|
||||||
|
surveyPath: 'EBzdmnSp',
|
||||||
|
code: {
|
||||||
|
dataConf: {
|
||||||
|
dataList: [
|
||||||
|
{
|
||||||
|
field: 'data458',
|
||||||
|
title: '<p>您的手机号</p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'data515',
|
||||||
|
title: '<p>您的性别</p>',
|
||||||
|
options: [
|
||||||
|
{
|
||||||
|
text: '<p>男</p>',
|
||||||
|
others: false,
|
||||||
|
mustOthers: false,
|
||||||
|
othersKey: '',
|
||||||
|
placeholderDesc: '',
|
||||||
|
hash: '115019',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
text: '<p>女</p>',
|
||||||
|
others: false,
|
||||||
|
mustOthers: false,
|
||||||
|
othersKey: '',
|
||||||
|
placeholderDesc: '',
|
||||||
|
hash: '115020',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'data450',
|
||||||
|
title: '<p>身份证</p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'data405',
|
||||||
|
title: '<p>地址</p>',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
field: 'data770',
|
||||||
|
title: '<p>邮箱</p>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
const result = getPushingData({
|
||||||
|
surveyResponse,
|
||||||
|
questionList: responseSchema?.code?.dataConf?.dataList || [],
|
||||||
|
surveyId: responseSchema.pageId,
|
||||||
|
surveyPath: responseSchema.surveyPath,
|
||||||
|
});
|
||||||
|
// Assertions
|
||||||
|
expect(result).toEqual({
|
||||||
|
surveyId: responseSchema.pageId,
|
||||||
|
surveyPath: responseSchema.surveyPath,
|
||||||
|
surveyResponseId: surveyResponse._id.toString(),
|
||||||
|
data: [
|
||||||
|
{
|
||||||
|
questionId: 'data458',
|
||||||
|
title: '<p>您的手机号</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['15000000000'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data515',
|
||||||
|
title: '<p>您的性别</p>',
|
||||||
|
valueType: 'option',
|
||||||
|
alias: '',
|
||||||
|
value: [
|
||||||
|
{
|
||||||
|
alias: '',
|
||||||
|
id: '115019',
|
||||||
|
text: '<p>男</p>',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data450',
|
||||||
|
title: '<p>身份证</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['450111000000000000'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data405',
|
||||||
|
title: '<p>地址</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['浙江省杭州市西湖区xxx'],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
questionId: 'data770',
|
||||||
|
title: '<p>邮箱</p>',
|
||||||
|
valueType: 'text',
|
||||||
|
alias: '',
|
||||||
|
value: ['123456@qq.com'],
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
87
server/src/utils/messagePushing.ts
Normal file
87
server/src/utils/messagePushing.ts
Normal file
@ -0,0 +1,87 @@
|
|||||||
|
export enum VALUE_TYPE {
|
||||||
|
TEXT = 'text',
|
||||||
|
OPTION = 'option',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 对问卷的题目列表和提交的数据进行组合
|
||||||
|
* @param param0.surveyResponse 回收的数据
|
||||||
|
* @param param0.responseSchema 问卷的配置
|
||||||
|
* @returns 组装好的数据
|
||||||
|
*/
|
||||||
|
export const getPushingData = ({
|
||||||
|
surveyResponse,
|
||||||
|
questionList,
|
||||||
|
surveyId,
|
||||||
|
surveyPath,
|
||||||
|
}) => {
|
||||||
|
const surveyResponseId = surveyResponse._id.toString();
|
||||||
|
const data = questionList
|
||||||
|
.filter((question) => {
|
||||||
|
const value = surveyResponse.data[question.field];
|
||||||
|
return value !== undefined;
|
||||||
|
})
|
||||||
|
.map((question) => {
|
||||||
|
// 遍历题目列表
|
||||||
|
let value = surveyResponse.data[question.field];
|
||||||
|
// 统一数组格式,不区分题型还有单选多选
|
||||||
|
value = Array.isArray(value) ? value : [value];
|
||||||
|
let valueType = VALUE_TYPE.TEXT;
|
||||||
|
const optionTextAndId = surveyResponse?.optionTextAndId?.[question.field];
|
||||||
|
if (Array.isArray(optionTextAndId) && optionTextAndId.length > 0) {
|
||||||
|
// 选项类的
|
||||||
|
value = value.map((val) => {
|
||||||
|
const index = optionTextAndId.findIndex((item) => item.hash === val);
|
||||||
|
if (index > -1) {
|
||||||
|
valueType = VALUE_TYPE.OPTION;
|
||||||
|
// 拿到选项id、选项文本和别名
|
||||||
|
const ret: Record<string, any> = {
|
||||||
|
alias: '',
|
||||||
|
id: optionTextAndId[index].hash,
|
||||||
|
text: optionTextAndId[index].text,
|
||||||
|
};
|
||||||
|
const extraKey = `${question.field}_${ret.id}`;
|
||||||
|
if (surveyResponse.data[extraKey]) {
|
||||||
|
// 更多输入框
|
||||||
|
ret.extraText = surveyResponse.data[extraKey];
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
}
|
||||||
|
return val;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (typeof value[0] === 'number') {
|
||||||
|
// 评分、nps类的
|
||||||
|
value = value.map((val) => {
|
||||||
|
valueType = VALUE_TYPE.OPTION;
|
||||||
|
const extraKey = `${question.field}_${val}`;
|
||||||
|
// 组装成选项类的格式
|
||||||
|
const ret: Record<string, any> = {
|
||||||
|
alias: '',
|
||||||
|
id: val,
|
||||||
|
text: val.toString(),
|
||||||
|
};
|
||||||
|
if (surveyResponse.data[extraKey]) {
|
||||||
|
// 更多输入框
|
||||||
|
ret.extraText = surveyResponse.data[extraKey];
|
||||||
|
}
|
||||||
|
return ret;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// 返回题目id、题目标题、数据类型、别名(目前未开放)、还有用户的答案
|
||||||
|
return {
|
||||||
|
questionId: question.field,
|
||||||
|
title: question.title,
|
||||||
|
valueType,
|
||||||
|
alias: '',
|
||||||
|
value,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
// 返回问卷id、问卷path、回收id和组装好的问卷和答案数据
|
||||||
|
return {
|
||||||
|
surveyId: surveyId,
|
||||||
|
surveyPath: surveyPath,
|
||||||
|
surveyResponseId,
|
||||||
|
data,
|
||||||
|
};
|
||||||
|
};
|
Loading…
Reference in New Issue
Block a user