feat: 新增数据推送功能 (#86)
This commit is contained in:
parent
b3b5fa9fac
commit
746bece538
@ -24,6 +24,7 @@
|
||||
"@nestjs/core": "^10.0.0",
|
||||
"@nestjs/platform-express": "^10.0.0",
|
||||
"@nestjs/serve-static": "^4.0.0",
|
||||
"@nestjs/swagger": "^7.3.0",
|
||||
"@nestjs/typeorm": "^10.0.1",
|
||||
"cheerio": "^1.0.0-rc.12",
|
||||
"crypto-js": "^4.2.0",
|
||||
@ -35,6 +36,7 @@
|
||||
"moment": "^2.30.1",
|
||||
"mongodb": "^5.9.2",
|
||||
"nanoid": "^3.3.7",
|
||||
"node-fetch": "^2.7.0",
|
||||
"node-forge": "^1.3.1",
|
||||
"reflect-metadata": "^0.1.13",
|
||||
"rxjs": "^7.8.1",
|
||||
|
@ -27,6 +27,8 @@ import { Counter } from './models/counter.entity';
|
||||
import { SurveyResponse } from './models/surveyResponse.entity';
|
||||
import { ClientEncrypt } from './models/clientEncrypt.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 { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
||||
@ -69,6 +71,8 @@ import { Logger } from './logger';
|
||||
ResponseSchema,
|
||||
ClientEncrypt,
|
||||
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 moment from 'moment';
|
||||
import { REQUEST } from '@nestjs/core';
|
||||
import { Inject, Request } from '@nestjs/common';
|
||||
import { Request } from 'express';
|
||||
const log4jsLogger = log4js.getLogger();
|
||||
|
||||
export class Logger {
|
||||
private static inited = false;
|
||||
|
||||
constructor(@Inject(REQUEST) private req: Request) {}
|
||||
constructor() {}
|
||||
|
||||
static init(config: { filename: string }) {
|
||||
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 level = options.level;
|
||||
const dltag = options.dltag ? `${options.dltag}||` : '';
|
||||
const traceIdStr = this.req['traceId']
|
||||
? `traceid=${this.req['traceId']}||`
|
||||
const traceIdStr = options?.req['traceId']
|
||||
? `traceid=${options?.req['traceId']}||`
|
||||
: '';
|
||||
return log4jsLogger[level](
|
||||
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
||||
);
|
||||
}
|
||||
|
||||
info(message, options = { dltag: '' }) {
|
||||
info(message, options?: { dltag?: string; req?: Request }) {
|
||||
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' });
|
||||
}
|
||||
}
|
||||
|
@ -1,9 +1,26 @@
|
||||
import { NestFactory } from '@nestjs/core';
|
||||
import { AppModule } from './app.module';
|
||||
import { SwaggerModule, DocumentBuilder } from '@nestjs/swagger';
|
||||
|
||||
async function bootstrap() {
|
||||
const PORT = process.env.PORT || 3000;
|
||||
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);
|
||||
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}`,
|
||||
{
|
||||
dltag: 'request_in',
|
||||
req,
|
||||
},
|
||||
);
|
||||
|
||||
@ -29,6 +30,7 @@ export class LogRequestMiddleware implements NestMiddleware {
|
||||
`status=${res.statusCode.toString()}||duration=${duration}ms`,
|
||||
{
|
||||
dltag: 'request_out',
|
||||
req,
|
||||
},
|
||||
);
|
||||
});
|
||||
|
@ -25,6 +25,6 @@ describe('BaseEntity', () => {
|
||||
const now = Date.now();
|
||||
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);
|
||||
});
|
||||
|
||||
// Add more test cases for different scenarios
|
||||
});
|
||||
|
||||
describe('checkCaptchaIsCorrect', () => {
|
||||
|
@ -1,14 +1,18 @@
|
||||
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 { 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 { HttpException } from 'src/exceptions/httpException';
|
||||
|
||||
import { create } from 'svg-captcha';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
|
||||
import { create } from 'svg-captcha';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
@ApiTags('auth')
|
||||
@Controller('/api/auth')
|
||||
export class AuthController {
|
||||
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 { ConfigService } from '@nestjs/config';
|
||||
import { ObjectId } from 'mongodb';
|
||||
|
||||
import { DataStatisticController } from '../controllers/dataStatistic.controller';
|
||||
import { DataStatisticService } from '../services/dataStatistic.service';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||
|
||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { UserService } from 'src/modules/auth/services/user.service';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||
|
||||
import { ObjectId } from 'mongodb';
|
||||
|
||||
jest.mock('../services/dataStatistic.service');
|
||||
jest.mock('../services/surveyMeta.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 { HttpException } from 'src/exceptions/httpException';
|
||||
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/surveyConf.service');
|
||||
jest.mock('../../surveyResponse/services/responseScheme.service');
|
||||
@ -37,6 +37,7 @@ describe('SurveyController', () => {
|
||||
ResponseSchemaService,
|
||||
ContentSecurityService,
|
||||
SurveyHistoryService,
|
||||
LoggerProvider,
|
||||
],
|
||||
}).compile();
|
||||
|
||||
|
@ -87,7 +87,6 @@ describe('SurveyConfService', () => {
|
||||
code: schema,
|
||||
} as unknown as SurveyConf);
|
||||
|
||||
// 调用待测试的方法
|
||||
await service.saveSurveyConf({ surveyId, schema });
|
||||
|
||||
// 验证save方法被调用了一次,并且传入了正确的参数
|
||||
@ -120,7 +119,6 @@ describe('SurveyConfService', () => {
|
||||
expect(surveyConfRepository.save).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
// getSurveyContentByCode方法的单元测试
|
||||
it('should get survey content by code', async () => {
|
||||
// 准备参数和模拟数据
|
||||
const schema = {
|
||||
|
@ -115,6 +115,10 @@ describe('SurveyHistoryService', () => {
|
||||
type: historyType,
|
||||
},
|
||||
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 { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import * as Joi from 'joi';
|
||||
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', () => {
|
||||
let controller: SurveyMetaController;
|
||||
@ -23,6 +25,7 @@ describe('SurveyMetaController', () => {
|
||||
.mockResolvedValue({ count: 0, data: [] }),
|
||||
},
|
||||
},
|
||||
LoggerProvider,
|
||||
],
|
||||
})
|
||||
.overrideGuard(Authtication)
|
||||
@ -74,9 +77,7 @@ describe('SurveyMetaController', () => {
|
||||
});
|
||||
|
||||
it('should validate request body with Joi', async () => {
|
||||
const reqBody = {
|
||||
// Missing title and surveyId
|
||||
};
|
||||
const reqBody = {};
|
||||
const req = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
@ -86,8 +87,8 @@ describe('SurveyMetaController', () => {
|
||||
try {
|
||||
await controller.updateMeta(reqBody, req);
|
||||
} catch (error) {
|
||||
expect(error).toBeInstanceOf(Joi.ValidationError);
|
||||
expect(error.details[0].message).toMatch('"title" is required');
|
||||
expect(error).toBeInstanceOf(HttpException);
|
||||
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
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 * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||
|
||||
@ApiTags('survey')
|
||||
@Controller('/api/survey/dataStatistic')
|
||||
export class DataStatisticController {
|
||||
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 BannerData from '../template/banner/index.json';
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { HISTORY_TYPE } from 'src/enums';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { Logger } from 'src/logger';
|
||||
|
||||
@ApiTags('survey')
|
||||
@Controller('/api/survey')
|
||||
export class SurveyController {
|
||||
constructor(
|
||||
@ -30,6 +34,7 @@ export class SurveyController {
|
||||
private readonly responseSchemaService: ResponseSchemaService,
|
||||
private readonly contentSecurityService: ContentSecurityService,
|
||||
private readonly surveyHistoryService: SurveyHistoryService,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@Get('/getBannerData')
|
||||
@ -68,6 +73,9 @@ export class SurveyController {
|
||||
}),
|
||||
}).validateAsync(reqBody);
|
||||
} catch (error) {
|
||||
this.logger.error(`createSurvey_parameter error: ${error.message}`, {
|
||||
req,
|
||||
});
|
||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
|
@ -11,8 +11,10 @@ import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
|
||||
@ApiTags('survey')
|
||||
@Controller('/api/surveyHisotry')
|
||||
export class SurveyHistoryController {
|
||||
constructor(
|
||||
|
@ -10,17 +10,23 @@ import {
|
||||
} from '@nestjs/common';
|
||||
import * as Joi from 'joi';
|
||||
import moment from 'moment';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { getFilter, getOrder } from 'src/utils/surveyUtil';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { Logger } from 'src/logger';
|
||||
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
|
||||
@ApiTags('survey')
|
||||
@Controller('/api/survey')
|
||||
export class SurveyMetaController {
|
||||
constructor(private readonly surveyMetaService: SurveyMetaService) {}
|
||||
constructor(
|
||||
private readonly surveyMetaService: SurveyMetaService,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Post('/updateMeta')
|
||||
@ -34,6 +40,9 @@ export class SurveyMetaController {
|
||||
surveyId: Joi.string().required(),
|
||||
}).validateAsync(reqBody, { allowUnknown: true });
|
||||
} catch (error) {
|
||||
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
||||
req,
|
||||
});
|
||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
|
@ -1,7 +1,8 @@
|
||||
import { Controller, Get, Res } from '@nestjs/common';
|
||||
import { Response } from 'express';
|
||||
import { join } from 'path';
|
||||
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
@ApiTags('ui')
|
||||
@Controller()
|
||||
export class SurveyUIController {
|
||||
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: {
|
||||
createDate: -1,
|
||||
},
|
||||
select: ['createDate', 'operator', 'type', '_id'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,8 @@ import { Module } from '@nestjs/common';
|
||||
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||
|
||||
import { SurveyResponseModule } from '../surveyResponse/surveyResponse.module';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
|
||||
@ -10,18 +12,21 @@ import { SurveyController } from './controllers/survey.controller';
|
||||
import { SurveyHistoryController } from './controllers/surveyHistory.controller';
|
||||
import { SurveyMetaController } from './controllers/surveyMeta.controller';
|
||||
import { SurveyUIController } from './controllers/surveyUI.controller';
|
||||
import { MessagePushingTaskController } from './controllers/messagePushingTask.controller';
|
||||
|
||||
import { SurveyConf } from 'src/models/surveyConf.entity';
|
||||
import { SurveyHistory } from 'src/models/surveyHistory.entity';
|
||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||
import { Word } from 'src/models/word.entity';
|
||||
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||
|
||||
import { DataStatisticService } from './services/dataStatistic.service';
|
||||
import { SurveyConfService } from './services/surveyConf.service';
|
||||
import { SurveyHistoryService } from './services/surveyHistory.service';
|
||||
import { SurveyMetaService } from './services/surveyMeta.service';
|
||||
import { ContentSecurityService } from './services/contentSecurity.service';
|
||||
import { MessagePushingTaskService } from './services/messagePushingTask.service';
|
||||
|
||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||
|
||||
@ -33,6 +38,7 @@ import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider
|
||||
SurveyHistory,
|
||||
SurveyResponse,
|
||||
Word,
|
||||
MessagePushingTask,
|
||||
]),
|
||||
ConfigModule,
|
||||
SurveyResponseModule,
|
||||
@ -44,6 +50,7 @@ import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider
|
||||
SurveyHistoryController,
|
||||
SurveyMetaController,
|
||||
SurveyUIController,
|
||||
MessagePushingTaskController,
|
||||
],
|
||||
providers: [
|
||||
DataStatisticService,
|
||||
@ -52,6 +59,8 @@ import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider
|
||||
SurveyMetaService,
|
||||
PluginManagerProvider,
|
||||
ContentSecurityService,
|
||||
MessagePushingTaskService,
|
||||
LoggerProvider,
|
||||
],
|
||||
})
|
||||
export class SurveyModule {}
|
||||
|
@ -113,7 +113,6 @@
|
||||
"type": "radio-star",
|
||||
"title": "标题2",
|
||||
"answer": "",
|
||||
"options": [],
|
||||
"textRange": {
|
||||
"min": {
|
||||
"placeholder": "0",
|
||||
|
@ -27,24 +27,6 @@
|
||||
"checked": false,
|
||||
"minNum": "",
|
||||
"maxNum": "",
|
||||
"options": [
|
||||
{
|
||||
"text": "选项1",
|
||||
"imageUrl": "",
|
||||
"others": false,
|
||||
"mustOthers": false,
|
||||
"othersKey": "",
|
||||
"placeholderDesc": ""
|
||||
},
|
||||
{
|
||||
"text": "选项2",
|
||||
"imageUrl": "",
|
||||
"others": false,
|
||||
"mustOthers": false,
|
||||
"othersKey": "",
|
||||
"placeholderDesc": ""
|
||||
}
|
||||
],
|
||||
"star": 5,
|
||||
"nps": {
|
||||
"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 { ObjectId } from 'mongodb';
|
||||
import { cloneDeep } from 'lodash';
|
||||
|
||||
import { mockResponseSchema } from './mockResponseSchema';
|
||||
|
||||
import { SurveyResponseController } from '../controllers/surveyResponse.controller';
|
||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||
import { CounterService } from '../services/counter.service';
|
||||
import { SurveyResponseService } from '../services/surveyResponse.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 { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||
import { cloneDeep } from 'lodash';
|
||||
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 = {
|
||||
surveyPath: 'EBzdmnSp',
|
||||
@ -65,9 +75,10 @@ const mockClientEncryptInfo = {
|
||||
describe('SurveyResponseController', () => {
|
||||
let controller: SurveyResponseController;
|
||||
let responseSchemaService: ResponseSchemaService;
|
||||
// let counterService: CounterService;
|
||||
let surveyResponseService: SurveyResponseService;
|
||||
let clientEncryptService: ClientEncryptService;
|
||||
let messagePushingTaskService: MessagePushingTaskService;
|
||||
let messagePushingLogService: MessagePushingLogService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -102,6 +113,18 @@ describe('SurveyResponseController', () => {
|
||||
.mockResolvedValue(mockClientEncryptInfo),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessagePushingTaskService,
|
||||
useValue: {
|
||||
findAll: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: MessagePushingLogService,
|
||||
useValue: {
|
||||
createPushingLog: jest.fn(),
|
||||
},
|
||||
},
|
||||
PluginManagerProvider,
|
||||
],
|
||||
}).compile();
|
||||
@ -110,13 +133,20 @@ describe('SurveyResponseController', () => {
|
||||
responseSchemaService = module.get<ResponseSchemaService>(
|
||||
ResponseSchemaService,
|
||||
);
|
||||
// counterService = module.get<CounterService>(CounterService);
|
||||
surveyResponseService = module.get<SurveyResponseService>(
|
||||
SurveyResponseService,
|
||||
);
|
||||
clientEncryptService =
|
||||
module.get<ClientEncryptService>(ClientEncryptService);
|
||||
|
||||
messagePushingTaskService = module.get<MessagePushingTaskService>(
|
||||
MessagePushingTaskService,
|
||||
);
|
||||
|
||||
messagePushingLogService = module.get<MessagePushingLogService>(
|
||||
MessagePushingLogService,
|
||||
);
|
||||
|
||||
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||
XiaojuSurveyPluginManager,
|
||||
);
|
||||
@ -141,10 +171,52 @@ describe('SurveyResponseController', () => {
|
||||
.mockResolvedValueOnce(0);
|
||||
jest
|
||||
.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
|
||||
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
||||
.mockResolvedValueOnce(undefined);
|
||||
jest
|
||||
.spyOn(controller, 'sendSurveyResponseMessage')
|
||||
.mockReturnValueOnce(undefined);
|
||||
|
||||
const result = await controller.createResponse(reqBody);
|
||||
|
||||
@ -166,7 +238,7 @@ describe('SurveyResponseController', () => {
|
||||
},
|
||||
clientTime: reqBody.clientTime,
|
||||
difTime: reqBody.difTime,
|
||||
surveyId: mockResponseSchema.pageId, // mock response schema 的 pageId
|
||||
surveyId: mockResponseSchema.pageId,
|
||||
optionTextAndId: {
|
||||
data515: [
|
||||
{
|
||||
@ -180,6 +252,7 @@ describe('SurveyResponseController', () => {
|
||||
],
|
||||
},
|
||||
});
|
||||
|
||||
expect(clientEncryptService.deleteEncryptInfo).toHaveBeenCalledWith(
|
||||
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', () => {
|
||||
const surveyPath = 'some-survey-path';
|
||||
const expectedFilePath = join(process.cwd(), 'public', 'render.html');
|
||||
|
||||
controller.render(surveyPath, res);
|
||||
controller.render(res);
|
||||
|
||||
expect(res.sendFile).toHaveBeenCalledWith(expectedFilePath);
|
||||
});
|
||||
|
@ -3,7 +3,9 @@ import { ConfigService } from '@nestjs/config';
|
||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||
import * as forge from 'node-forge';
|
||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('surveyResponse')
|
||||
@Controller('/api/clientEncrypt')
|
||||
export class ClientEncryptController {
|
||||
constructor(
|
||||
|
@ -2,7 +2,9 @@ import { Controller, Get, HttpCode, Query } from '@nestjs/common';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { CounterService } from '../services/counter.service';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('surveyResponse')
|
||||
@Controller('/api/counter')
|
||||
export class CounterController {
|
||||
constructor(private readonly counterService: CounterService) {}
|
||||
|
@ -3,7 +3,9 @@ import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { RECORD_STATUS } from 'src/enums';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
@ApiTags('surveyResponse')
|
||||
@Controller('/api/responseSchema')
|
||||
export class ResponseSchemaController {
|
||||
constructor(private readonly responseSchemaService: ResponseSchemaService) {}
|
||||
|
@ -2,16 +2,28 @@ import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||
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 {
|
||||
MESSAGE_PUSHING_HOOK,
|
||||
MESSAGE_PUSHING_TYPE,
|
||||
} from 'src/enums/messagePushing';
|
||||
import { getPushingData } from 'src/utils/messagePushing';
|
||||
|
||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||
import { CounterService } from '../services/counter.service';
|
||||
import moment from 'moment';
|
||||
import { SurveyResponseService } from '../services/surveyResponse.service';
|
||||
import { ClientEncryptService } from '../services/clientEncrypt.service';
|
||||
import { ENCRYPT_TYPE } from 'src/enums/encrypt';
|
||||
import * as forge from 'node-forge';
|
||||
import { MessagePushingTaskService } from '../../survey/services/messagePushingTask.service';
|
||||
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')
|
||||
export class SurveyResponseController {
|
||||
constructor(
|
||||
@ -19,6 +31,8 @@ export class SurveyResponseController {
|
||||
private readonly counterService: CounterService,
|
||||
private readonly surveyResponseService: SurveyResponseService,
|
||||
private readonly clientEncryptService: ClientEncryptService,
|
||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||
private readonly messagePushingLogService: MessagePushingLogService,
|
||||
) {}
|
||||
|
||||
@Post('/createResponse')
|
||||
@ -174,13 +188,27 @@ export class SurveyResponseController {
|
||||
}
|
||||
|
||||
// 入库
|
||||
await this.surveyResponseService.createSurveyResponse({
|
||||
surveyPath: validationResult.surveyPath,
|
||||
data: decryptedData,
|
||||
clientTime,
|
||||
difTime,
|
||||
const surveyResponse =
|
||||
await this.surveyResponseService.createSurveyResponse({
|
||||
surveyPath: validationResult.surveyPath,
|
||||
data: decryptedData,
|
||||
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,
|
||||
optionTextAndId,
|
||||
});
|
||||
|
||||
// 入库成功后,要把密钥删掉,防止被重复使用
|
||||
@ -191,4 +219,53 @@ export class SurveyResponseController {
|
||||
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 { join } from 'path';
|
||||
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
@ApiTags('ui')
|
||||
@Controller()
|
||||
export class SurveyResponseUIController {
|
||||
constructor() {}
|
||||
|
||||
@Get('/render/:surveyPath')
|
||||
render(@Param('surveyPath') surveyPath: string, @Res() res: Response) {
|
||||
@Get('/render/:path*')
|
||||
render(@Res() res: Response) {
|
||||
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) {
|
||||
|
@ -4,11 +4,15 @@ import { ResponseSchemaService } from './services/responseScheme.service';
|
||||
import { SurveyResponseService } from './services/surveyResponse.service';
|
||||
import { CounterService } from './services/counter.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 { Counter } from 'src/models/counter.entity';
|
||||
import { SurveyResponse } from 'src/models/surveyResponse.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 { CounterController } from './controllers/counter.controller';
|
||||
@ -26,6 +30,8 @@ import { ConfigModule } from '@nestjs/config';
|
||||
Counter,
|
||||
SurveyResponse,
|
||||
ClientEncrypt,
|
||||
MessagePushingTask,
|
||||
MessagePushingLog,
|
||||
]),
|
||||
ConfigModule,
|
||||
],
|
||||
@ -41,6 +47,8 @@ import { ConfigModule } from '@nestjs/config';
|
||||
SurveyResponseService,
|
||||
CounterService,
|
||||
ClientEncryptService,
|
||||
MessagePushingTaskService,
|
||||
MessagePushingLogService,
|
||||
],
|
||||
exports: [
|
||||
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