feat: 新增空间和协作功能 (#252)
This commit is contained in:
parent
17b84ef501
commit
f9d75962ed
@ -13,6 +13,7 @@ import { SurveyResponseModule } from './modules/surveyResponse/surveyResponse.mo
|
|||||||
import { AuthModule } from './modules/auth/auth.module';
|
import { AuthModule } from './modules/auth/auth.module';
|
||||||
import { MessageModule } from './modules/message/message.module';
|
import { MessageModule } from './modules/message/message.module';
|
||||||
import { FileModule } from './modules/file/file.module';
|
import { FileModule } from './modules/file/file.module';
|
||||||
|
import { WorkspaceModule } from './modules/workspace/workspace.module';
|
||||||
|
|
||||||
import { join } from 'path';
|
import { join } from 'path';
|
||||||
|
|
||||||
@ -31,6 +32,9 @@ 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 { MessagePushingTask } from './models/messagePushingTask.entity';
|
||||||
import { MessagePushingLog } from './models/messagePushingLog.entity';
|
import { MessagePushingLog } from './models/messagePushingLog.entity';
|
||||||
|
import { WorkspaceMember } from './models/workspaceMember.entity';
|
||||||
|
import { Workspace } from './models/workspace.entity';
|
||||||
|
import { Collaborator } from './models/collaborator.entity';
|
||||||
|
|
||||||
import { LoggerProvider } from './logger/logger.provider';
|
import { LoggerProvider } from './logger/logger.provider';
|
||||||
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
||||||
@ -74,6 +78,9 @@ import { Logger } from './logger';
|
|||||||
Word,
|
Word,
|
||||||
MessagePushingTask,
|
MessagePushingTask,
|
||||||
MessagePushingLog,
|
MessagePushingLog,
|
||||||
|
Workspace,
|
||||||
|
WorkspaceMember,
|
||||||
|
Collaborator,
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
@ -92,6 +99,7 @@ import { Logger } from './logger';
|
|||||||
}),
|
}),
|
||||||
MessageModule,
|
MessageModule,
|
||||||
FileModule,
|
FileModule,
|
||||||
|
WorkspaceModule,
|
||||||
],
|
],
|
||||||
controllers: [AppController],
|
controllers: [AppController],
|
||||||
providers: [
|
providers: [
|
||||||
|
@ -1,6 +1,8 @@
|
|||||||
export enum EXCEPTION_CODE {
|
export enum EXCEPTION_CODE {
|
||||||
AUTHENTICATION_FAILED = 1001, // 没有权限
|
AUTHENTICATION_FAILED = 1001, // 未授权
|
||||||
PARAMETER_ERROR = 1002, // 参数有误
|
PARAMETER_ERROR = 1002, // 参数有误
|
||||||
|
NO_PERMISSION = 1003, // 没有操作权限
|
||||||
|
|
||||||
USER_EXISTS = 2001, // 用户已存在
|
USER_EXISTS = 2001, // 用户已存在
|
||||||
USER_NOT_EXISTS = 2002, // 用户不存在
|
USER_NOT_EXISTS = 2002, // 用户不存在
|
||||||
USER_PASSWORD_WRONG = 2003, // 用户名或密码错误
|
USER_PASSWORD_WRONG = 2003, // 用户名或密码错误
|
||||||
|
20
server/src/enums/surveyPermission.ts
Normal file
20
server/src/enums/surveyPermission.ts
Normal file
@ -0,0 +1,20 @@
|
|||||||
|
export enum SURVEY_PERMISSION {
|
||||||
|
SURVEY_CONF_MANAGE = 'SURVEY_CONF_MANAGE',
|
||||||
|
SURVEY_RESPONSE_MANAGE = 'SURVEY_RESPONSE_MANAGE',
|
||||||
|
SURVEY_COOPERATION_MANAGE = 'SURVEY_COOPERATION_MANAGE',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const SURVEY_PERMISSION_DESCRIPTION = {
|
||||||
|
SURVEY_CONF_MANAGE: {
|
||||||
|
name: '问卷配置管理',
|
||||||
|
value: SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
},
|
||||||
|
surveyResponseManage: {
|
||||||
|
name: '问卷分析管理',
|
||||||
|
value: SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
},
|
||||||
|
surveyCooperatorManage: {
|
||||||
|
name: '协作者管理',
|
||||||
|
value: SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
},
|
||||||
|
};
|
41
server/src/enums/workspace.ts
Normal file
41
server/src/enums/workspace.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
export enum ROLE {
|
||||||
|
ADMIN = 'admin',
|
||||||
|
USER = 'user',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_DESCRIPTION = {
|
||||||
|
ADMIN: {
|
||||||
|
name: '管理员',
|
||||||
|
value: ROLE.ADMIN,
|
||||||
|
},
|
||||||
|
USER: {
|
||||||
|
name: '用户',
|
||||||
|
value: ROLE.USER,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
export enum PERMISSION {
|
||||||
|
READ_WORKSPACE = 'READ_WORKSPACE',
|
||||||
|
WRITE_WORKSPACE = 'WRITE_WORKSPACE',
|
||||||
|
READ_MEMBER = 'READ_MEMBER',
|
||||||
|
WRITE_MEMBER = 'WRITE_MEMBER',
|
||||||
|
READ_SURVEY = 'READ_SURVEY',
|
||||||
|
WRITE_SURVEY = 'WRITE_SURVEY',
|
||||||
|
}
|
||||||
|
|
||||||
|
export const ROLE_PERMISSION: Record<ROLE, PERMISSION[]> = {
|
||||||
|
[ROLE.ADMIN]: [
|
||||||
|
PERMISSION.READ_WORKSPACE,
|
||||||
|
PERMISSION.WRITE_WORKSPACE,
|
||||||
|
PERMISSION.READ_MEMBER,
|
||||||
|
PERMISSION.WRITE_MEMBER,
|
||||||
|
PERMISSION.READ_SURVEY,
|
||||||
|
PERMISSION.WRITE_SURVEY,
|
||||||
|
],
|
||||||
|
[ROLE.USER]: [
|
||||||
|
PERMISSION.READ_WORKSPACE,
|
||||||
|
PERMISSION.READ_MEMBER,
|
||||||
|
PERMISSION.READ_SURVEY,
|
||||||
|
PERMISSION.WRITE_SURVEY,
|
||||||
|
],
|
||||||
|
};
|
55
server/src/exceptions/__test/httpExceptions.filter.spec.ts
Normal file
55
server/src/exceptions/__test/httpExceptions.filter.spec.ts
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { HttpExceptionsFilter } from '../httpExceptions.filter';
|
||||||
|
import { ArgumentsHost } from '@nestjs/common';
|
||||||
|
import { HttpException } from '../httpException';
|
||||||
|
import { Response } from 'express';
|
||||||
|
|
||||||
|
describe('HttpExceptionsFilter', () => {
|
||||||
|
let filter: HttpExceptionsFilter;
|
||||||
|
let mockArgumentsHost: ArgumentsHost;
|
||||||
|
let mockResponse: Partial<Response>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [HttpExceptionsFilter],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
filter = module.get<HttpExceptionsFilter>(HttpExceptionsFilter);
|
||||||
|
|
||||||
|
mockResponse = {
|
||||||
|
status: jest.fn().mockReturnThis(),
|
||||||
|
json: jest.fn(),
|
||||||
|
};
|
||||||
|
|
||||||
|
mockArgumentsHost = {
|
||||||
|
switchToHttp: jest.fn().mockReturnThis(),
|
||||||
|
getResponse: jest.fn().mockReturnValue(mockResponse),
|
||||||
|
} as unknown as ArgumentsHost;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 500 status and "Internal Server Error" message for generic errors', () => {
|
||||||
|
const genericError = new Error('Some error');
|
||||||
|
|
||||||
|
filter.catch(genericError, mockArgumentsHost);
|
||||||
|
|
||||||
|
expect(mockResponse.status).toHaveBeenCalledWith(500);
|
||||||
|
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||||
|
message: 'Internal Server Error',
|
||||||
|
code: 500,
|
||||||
|
errmsg: 'Some error',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return 200 status and specific message for HttpException', () => {
|
||||||
|
const httpException = new HttpException('Specific error message', 1001);
|
||||||
|
|
||||||
|
filter.catch(httpException, mockArgumentsHost);
|
||||||
|
|
||||||
|
expect(mockResponse.status).toHaveBeenCalledWith(200);
|
||||||
|
expect(mockResponse.json).toHaveBeenCalledWith({
|
||||||
|
message: 'Specific error message',
|
||||||
|
code: 1001,
|
||||||
|
errmsg: 'Specific error message',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -1,4 +1,3 @@
|
|||||||
// all-exceptions.filter.ts
|
|
||||||
import {
|
import {
|
||||||
ExceptionFilter,
|
ExceptionFilter,
|
||||||
Catch,
|
Catch,
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { HttpException } from './httpException';
|
import { HttpException } from './httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
|
||||||
export class NoSurveyPermissionException extends HttpException {
|
export class NoPermissionException extends HttpException {
|
||||||
constructor(public readonly message: string) {
|
constructor(public readonly message: string) {
|
||||||
super(message, EXCEPTION_CODE.NO_SURVEY_PERMISSION);
|
super(message, EXCEPTION_CODE.NO_PERMISSION);
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -1,21 +1,21 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { ConfigService } from '@nestjs/config';
|
import { ConfigService } from '@nestjs/config';
|
||||||
import { Authtication } from './authtication';
|
import { Authentication } from '../authentication.guard';
|
||||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
import { AuthenticationException } from 'src/exceptions/authException';
|
import { AuthenticationException } from 'src/exceptions/authException';
|
||||||
import { User } from 'src/models/user.entity';
|
import { User } from 'src/models/user.entity';
|
||||||
|
|
||||||
jest.mock('jsonwebtoken');
|
jest.mock('jsonwebtoken');
|
||||||
|
|
||||||
describe('Authtication', () => {
|
describe('Authentication', () => {
|
||||||
let guard: Authtication;
|
let guard: Authentication;
|
||||||
let authService: AuthService;
|
let authService: AuthService;
|
||||||
let configService: ConfigService;
|
let configService: ConfigService;
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
providers: [
|
providers: [
|
||||||
Authtication,
|
Authentication,
|
||||||
{
|
{
|
||||||
provide: AuthService,
|
provide: AuthService,
|
||||||
useValue: {
|
useValue: {
|
||||||
@ -31,7 +31,7 @@ describe('Authtication', () => {
|
|||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
guard = module.get<Authtication>(Authtication);
|
guard = module.get<Authentication>(Authentication);
|
||||||
authService = module.get<AuthService>(AuthService);
|
authService = module.get<AuthService>(AuthService);
|
||||||
configService = module.get<ConfigService>(ConfigService);
|
configService = module.get<ConfigService>(ConfigService);
|
||||||
});
|
});
|
139
server/src/guards/__test/survey.guard.spec.ts
Normal file
139
server/src/guards/__test/survey.guard.spec.ts
Normal file
@ -0,0 +1,139 @@
|
|||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ExecutionContext } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { SurveyGuard } from '../survey.guard';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
|
||||||
|
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||||
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
|
|
||||||
|
describe('SurveyGuard', () => {
|
||||||
|
let guard: SurveyGuard;
|
||||||
|
let reflector: Reflector;
|
||||||
|
let collaboratorService: CollaboratorService;
|
||||||
|
let surveyMetaService: SurveyMetaService;
|
||||||
|
let workspaceMemberService: WorkspaceMemberService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
SurveyGuard,
|
||||||
|
{
|
||||||
|
provide: Reflector,
|
||||||
|
useValue: {
|
||||||
|
get: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: CollaboratorService,
|
||||||
|
useValue: {
|
||||||
|
getCollaborator: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SurveyMetaService,
|
||||||
|
useValue: {
|
||||||
|
getSurveyById: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {
|
||||||
|
findOne: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
guard = module.get<SurveyGuard>(SurveyGuard);
|
||||||
|
reflector = module.get<Reflector>(Reflector);
|
||||||
|
collaboratorService = module.get<CollaboratorService>(CollaboratorService);
|
||||||
|
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
||||||
|
workspaceMemberService = module.get<WorkspaceMemberService>(
|
||||||
|
WorkspaceMemberService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(guard).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access if no surveyId is present', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue(null);
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw SurveyNotFoundException if survey does not exist', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue('params.surveyId');
|
||||||
|
jest.spyOn(surveyMetaService, 'getSurveyById').mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
SurveyNotFoundException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access if user is the owner of the survey', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
const surveyMeta = { owner: 'testUser', workspaceId: null };
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue('params.surveyId');
|
||||||
|
jest
|
||||||
|
.spyOn(surveyMetaService, 'getSurveyById')
|
||||||
|
.mockResolvedValue(surveyMeta as SurveyMeta);
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access if user is a workspace member', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
const surveyMeta = { owner: 'anotherUser', workspaceId: 'workspaceId' };
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue('params.surveyId');
|
||||||
|
jest
|
||||||
|
.spyOn(surveyMetaService, 'getSurveyById')
|
||||||
|
.mockResolvedValue(surveyMeta as SurveyMeta);
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'findOne')
|
||||||
|
.mockResolvedValue({} as WorkspaceMember);
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoPermissionException if user has no permissions', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
const surveyMeta = { owner: 'anotherUser', workspaceId: null };
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValueOnce('params.surveyId');
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValueOnce(['requiredPermission']);
|
||||||
|
jest
|
||||||
|
.spyOn(surveyMetaService, 'getSurveyById')
|
||||||
|
.mockResolvedValue(surveyMeta as SurveyMeta);
|
||||||
|
jest
|
||||||
|
.spyOn(collaboratorService, 'getCollaborator')
|
||||||
|
.mockResolvedValue({ permissions: [] } as Collaborator);
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
NoPermissionException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockExecutionContext(): ExecutionContext {
|
||||||
|
return {
|
||||||
|
switchToHttp: jest.fn().mockReturnValue({
|
||||||
|
getRequest: jest.fn().mockReturnValue({
|
||||||
|
user: { username: 'testUser', _id: 'testUserId' },
|
||||||
|
params: { surveyId: 'surveyId' },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
getHandler: jest.fn(),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
}
|
||||||
|
});
|
137
server/src/guards/__test/workspace.guard.spec.ts
Normal file
137
server/src/guards/__test/workspace.guard.spec.ts
Normal file
@ -0,0 +1,137 @@
|
|||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ExecutionContext } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { WorkspaceGuard } from '../workspace.guard';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { NoPermissionException } from '../../exceptions/noPermissionException';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
|
||||||
|
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
describe('WorkspaceGuard', () => {
|
||||||
|
let guard: WorkspaceGuard;
|
||||||
|
let reflector: Reflector;
|
||||||
|
let workspaceMemberService: WorkspaceMemberService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
WorkspaceGuard,
|
||||||
|
{
|
||||||
|
provide: Reflector,
|
||||||
|
useValue: {
|
||||||
|
get: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {
|
||||||
|
findOne: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
guard = module.get<WorkspaceGuard>(WorkspaceGuard);
|
||||||
|
reflector = module.get<Reflector>(Reflector);
|
||||||
|
workspaceMemberService = module.get<WorkspaceMemberService>(
|
||||||
|
WorkspaceMemberService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(guard).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access if no roles are defined', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValue(null);
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoPermissionException if workspaceId is missing and optional is false', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest
|
||||||
|
.spyOn(reflector, 'get')
|
||||||
|
.mockReturnValueOnce([WORKSPACE_PERMISSION.READ_WORKSPACE]);
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValueOnce('params.workspaceId');
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
NoPermissionException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access if workspaceId is missing and optional is true', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest
|
||||||
|
.spyOn(reflector, 'get')
|
||||||
|
.mockReturnValueOnce([WORKSPACE_PERMISSION.WRITE_WORKSPACE]);
|
||||||
|
jest
|
||||||
|
.spyOn(reflector, 'get')
|
||||||
|
.mockReturnValueOnce({ key: 'params.workspaceId', optional: true });
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'findOne')
|
||||||
|
.mockResolvedValue({ role: 'admin' } as WorkspaceMember);
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoPermissionException if user is not a member of the workspace', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest
|
||||||
|
.spyOn(reflector, 'get')
|
||||||
|
.mockReturnValueOnce([WORKSPACE_PERMISSION.WRITE_WORKSPACE]);
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValueOnce('params.workspaceId');
|
||||||
|
jest.spyOn(workspaceMemberService, 'findOne').mockResolvedValue(null);
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
NoPermissionException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw NoPermissionException if user role is not allowed', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest
|
||||||
|
.spyOn(reflector, 'get')
|
||||||
|
.mockReturnValueOnce([WORKSPACE_PERMISSION.READ_MEMBER]);
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValueOnce('params.workspaceId');
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'findOne')
|
||||||
|
.mockResolvedValue({ role: 'member' } as WorkspaceMember);
|
||||||
|
|
||||||
|
await expect(guard.canActivate(context)).rejects.toThrow(
|
||||||
|
NoPermissionException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should allow access if user role is allowed', async () => {
|
||||||
|
const context = createMockExecutionContext();
|
||||||
|
jest
|
||||||
|
.spyOn(reflector, 'get')
|
||||||
|
.mockReturnValueOnce([WORKSPACE_PERMISSION.READ_MEMBER]);
|
||||||
|
jest.spyOn(reflector, 'get').mockReturnValueOnce('params.workspaceId');
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'findOne')
|
||||||
|
.mockResolvedValue({ role: 'admin' } as WorkspaceMember);
|
||||||
|
|
||||||
|
const result = await guard.canActivate(context);
|
||||||
|
expect(result).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
function createMockExecutionContext(): ExecutionContext {
|
||||||
|
return {
|
||||||
|
switchToHttp: jest.fn().mockReturnValue({
|
||||||
|
getRequest: jest.fn().mockReturnValue({
|
||||||
|
user: { _id: 'testUserId' },
|
||||||
|
params: { workspaceId: 'workspaceId' },
|
||||||
|
}),
|
||||||
|
}),
|
||||||
|
getHandler: jest.fn(),
|
||||||
|
} as unknown as ExecutionContext;
|
||||||
|
}
|
||||||
|
});
|
@ -3,7 +3,7 @@ import { AuthenticationException } from '../exceptions/authException';
|
|||||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class Authtication implements CanActivate {
|
export class Authentication implements CanActivate {
|
||||||
constructor(private readonly authService: AuthService) {}
|
constructor(private readonly authService: AuthService) {}
|
||||||
|
|
||||||
async canActivate(context: ExecutionContext): Promise<boolean> {
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
88
server/src/guards/survey.guard.ts
Normal file
88
server/src/guards/survey.guard.ts
Normal file
@ -0,0 +1,88 @@
|
|||||||
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
|
||||||
|
import { CollaboratorService } from 'src/modules/survey/services/collaborator.service';
|
||||||
|
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||||
|
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||||
|
import { NoPermissionException } from 'src/exceptions/noPermissionException';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class SurveyGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private reflector: Reflector,
|
||||||
|
private readonly collaboratorService: CollaboratorService,
|
||||||
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const user = request.user;
|
||||||
|
const surveyIdKey = this.reflector.get<string>(
|
||||||
|
'surveyId',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
const surveyId = get(request, surveyIdKey);
|
||||||
|
|
||||||
|
if (!surveyId) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
|
||||||
|
|
||||||
|
if (!surveyMeta) {
|
||||||
|
throw new SurveyNotFoundException('问卷不存在');
|
||||||
|
}
|
||||||
|
|
||||||
|
request.surveyMeta = surveyMeta;
|
||||||
|
|
||||||
|
// 兼容老的问卷没有ownerId
|
||||||
|
if (
|
||||||
|
surveyMeta.ownerId === user._id.toString() ||
|
||||||
|
surveyMeta.owner === user.username
|
||||||
|
) {
|
||||||
|
// 问卷的owner,可以访问和操作问卷
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (surveyMeta.workspaceId) {
|
||||||
|
const memberInfo = await this.workspaceMemberService.findOne({
|
||||||
|
workspaceId: surveyMeta.workspaceId,
|
||||||
|
userId: user._id.toString(),
|
||||||
|
});
|
||||||
|
if (!memberInfo) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
const permissions = this.reflector.get<string[]>(
|
||||||
|
'surveyPermission',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!Array.isArray(permissions) || permissions.length === 0) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
const info = await this.collaboratorService.getCollaborator({
|
||||||
|
surveyId,
|
||||||
|
userId: user._id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!info) {
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
request.collaborator = info;
|
||||||
|
if (
|
||||||
|
permissions.some((permission) => info.permissions.includes(permission))
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
}
|
72
server/src/guards/workspace.guard.ts
Normal file
72
server/src/guards/workspace.guard.ts
Normal file
@ -0,0 +1,72 @@
|
|||||||
|
import { Injectable, CanActivate, ExecutionContext } from '@nestjs/common';
|
||||||
|
import { Reflector } from '@nestjs/core';
|
||||||
|
import { get } from 'lodash';
|
||||||
|
|
||||||
|
import { NoPermissionException } from '../exceptions/noPermissionException';
|
||||||
|
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { ROLE_PERMISSION as WORKSPACE_ROLE_PERMISSION } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceGuard implements CanActivate {
|
||||||
|
constructor(
|
||||||
|
private reflector: Reflector,
|
||||||
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async canActivate(context: ExecutionContext): Promise<boolean> {
|
||||||
|
const allowPermissions = this.reflector.get<string[]>(
|
||||||
|
'workspacePermissions',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
if (!allowPermissions) {
|
||||||
|
return true; // 没有定义权限,可以访问
|
||||||
|
}
|
||||||
|
|
||||||
|
const request = context.switchToHttp().getRequest();
|
||||||
|
const user = request.user;
|
||||||
|
const workspaceIdInfo = this.reflector.get(
|
||||||
|
'workspaceId',
|
||||||
|
context.getHandler(),
|
||||||
|
);
|
||||||
|
|
||||||
|
let workspaceIdKey, optional;
|
||||||
|
if (typeof workspaceIdInfo === 'string') {
|
||||||
|
workspaceIdKey = workspaceIdInfo;
|
||||||
|
optional = false;
|
||||||
|
} else {
|
||||||
|
workspaceIdKey = workspaceIdInfo?.key;
|
||||||
|
optional = workspaceIdInfo?.optional || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const workspaceId = get(request, workspaceIdKey);
|
||||||
|
|
||||||
|
if (!workspaceId && optional === false) {
|
||||||
|
throw new NoPermissionException('没有空间权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
if (workspaceId) {
|
||||||
|
const membersInfo = await this.workspaceMemberService.findOne({
|
||||||
|
workspaceId,
|
||||||
|
userId: user._id.toString(),
|
||||||
|
});
|
||||||
|
|
||||||
|
if (!membersInfo) {
|
||||||
|
throw new NoPermissionException('没有空间权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
const userPermissions = WORKSPACE_ROLE_PERMISSION[membersInfo.role] || [];
|
||||||
|
if (
|
||||||
|
allowPermissions.some((permission) =>
|
||||||
|
userPermissions.includes(permission),
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
throw new NoPermissionException('没有权限');
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
@ -34,10 +34,10 @@ export class Logger {
|
|||||||
|
|
||||||
_log(message, options: { dltag?: string; level: string; req?: Request }) {
|
_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 = options?.req['traceId']
|
const traceIdStr = options?.req?.['traceId']
|
||||||
? `traceid=${options?.req['traceId']}||`
|
? `traceid=${options?.req?.['traceId']}||`
|
||||||
: '';
|
: '';
|
||||||
return log4jsLogger[level](
|
return log4jsLogger[level](
|
||||||
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
`[${datetime}][${level.toUpperCase()}]${dltag}${traceIdStr}${message}`,
|
||||||
|
14
server/src/models/collaborator.entity.ts
Normal file
14
server/src/models/collaborator.entity.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Entity, Column } from 'typeorm';
|
||||||
|
import { BaseEntity } from './base.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'collaborator' })
|
||||||
|
export class Collaborator extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column('jsonb')
|
||||||
|
permissions: Array<string>;
|
||||||
|
}
|
@ -21,9 +21,15 @@ export class SurveyMeta extends BaseEntity {
|
|||||||
@Column()
|
@Column()
|
||||||
owner: string;
|
owner: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
ownerId: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
createMethod: string;
|
createMethod: string;
|
||||||
|
|
||||||
@Column()
|
@Column()
|
||||||
createFrom: string;
|
createFrom: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workspaceId: string;
|
||||||
}
|
}
|
||||||
|
14
server/src/models/workspace.entity.ts
Normal file
14
server/src/models/workspace.entity.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Entity, Column } from 'typeorm';
|
||||||
|
import { BaseEntity } from './base.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'workspace' })
|
||||||
|
export class Workspace extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
ownerId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
description: string;
|
||||||
|
}
|
14
server/src/models/workspaceMember.entity.ts
Normal file
14
server/src/models/workspaceMember.entity.ts
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
import { Entity, Column } from 'typeorm';
|
||||||
|
import { BaseEntity } from './base.entity';
|
||||||
|
|
||||||
|
@Entity({ name: 'workspaceMember' })
|
||||||
|
export class WorkspaceMember extends BaseEntity {
|
||||||
|
@Column()
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
workspaceId: string;
|
||||||
|
|
||||||
|
@Column()
|
||||||
|
role: string;
|
||||||
|
}
|
82
server/src/modules/auth/__test/user.controller.spec.ts
Normal file
82
server/src/modules/auth/__test/user.controller.spec.ts
Normal file
@ -0,0 +1,82 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { UserController } from '../controllers/user.controller';
|
||||||
|
import { UserService } from '../services/user.service';
|
||||||
|
import { GetUserListDto } from '../dto/getUserList.dto';
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { User } from 'src/models/user.entity';
|
||||||
|
|
||||||
|
describe('UserController', () => {
|
||||||
|
let userController: UserController;
|
||||||
|
let userService: UserService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [UserController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useValue: {
|
||||||
|
getUserListByUsername: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
})
|
||||||
|
.overrideGuard(Authentication)
|
||||||
|
.useValue({ canActivate: jest.fn(() => true) })
|
||||||
|
.compile();
|
||||||
|
|
||||||
|
userController = module.get<UserController>(UserController);
|
||||||
|
userService = module.get<UserService>(UserService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getUserList', () => {
|
||||||
|
it('should return a list of users', async () => {
|
||||||
|
const mockUserList = [
|
||||||
|
{ _id: '1', username: 'user1' },
|
||||||
|
{ _id: '2', username: 'user2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(userService, 'getUserListByUsername')
|
||||||
|
.mockResolvedValue(mockUserList as unknown as User[]);
|
||||||
|
|
||||||
|
const queryInfo: GetUserListDto = {
|
||||||
|
username: 'testuser',
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
GetUserListDto.validate = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ value: queryInfo, error: null });
|
||||||
|
|
||||||
|
const result = await userController.getUserList(queryInfo);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: mockUserList.map((item) => ({
|
||||||
|
userId: item._id,
|
||||||
|
username: item.username,
|
||||||
|
})),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an HttpException if validation fails', async () => {
|
||||||
|
const queryInfo: GetUserListDto = {
|
||||||
|
username: 'testuser',
|
||||||
|
pageIndex: 1,
|
||||||
|
pageSize: 10,
|
||||||
|
};
|
||||||
|
const validationError = new Error('Validation failed');
|
||||||
|
|
||||||
|
GetUserListDto.validate = jest
|
||||||
|
.fn()
|
||||||
|
.mockReturnValue({ value: null, error: validationError });
|
||||||
|
|
||||||
|
await expect(userController.getUserList(queryInfo)).rejects.toThrow(
|
||||||
|
new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -5,6 +5,7 @@ import { UserService } from '../services/user.service';
|
|||||||
import { User } from 'src/models/user.entity';
|
import { User } from 'src/models/user.entity';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { hash256 } from 'src/utils/hash256';
|
import { hash256 } from 'src/utils/hash256';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
describe('UserService', () => {
|
describe('UserService', () => {
|
||||||
let service: UserService;
|
let service: UserService;
|
||||||
@ -135,7 +136,10 @@ describe('UserService', () => {
|
|||||||
const user = await service.getUserByUsername(username);
|
const user = await service.getUserByUsername(username);
|
||||||
|
|
||||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
expect(userRepository.findOne).toHaveBeenCalledWith({
|
||||||
where: { username: username },
|
where: {
|
||||||
|
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||||
|
username: username,
|
||||||
|
},
|
||||||
});
|
});
|
||||||
expect(user).toEqual(userInfo);
|
expect(user).toEqual(userInfo);
|
||||||
});
|
});
|
||||||
|
@ -4,6 +4,7 @@ import { AuthService } from './services/auth.service';
|
|||||||
import { CaptchaService } from './services/captcha.service';
|
import { CaptchaService } from './services/captcha.service';
|
||||||
|
|
||||||
import { AuthController } from './controllers/auth.controller';
|
import { AuthController } from './controllers/auth.controller';
|
||||||
|
import { UserController } from './controllers/user.controller';
|
||||||
|
|
||||||
import { User } from 'src/models/user.entity';
|
import { User } from 'src/models/user.entity';
|
||||||
import { Captcha } from 'src/models/captcha.entity';
|
import { Captcha } from 'src/models/captcha.entity';
|
||||||
@ -13,7 +14,7 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [TypeOrmModule.forFeature([User, Captcha]), ConfigModule],
|
imports: [TypeOrmModule.forFeature([User, Captcha]), ConfigModule],
|
||||||
controllers: [AuthController],
|
controllers: [AuthController, UserController],
|
||||||
providers: [UserService, AuthService, CaptchaService],
|
providers: [UserService, AuthService, CaptchaService],
|
||||||
exports: [UserService, AuthService],
|
exports: [UserService, AuthService],
|
||||||
})
|
})
|
||||||
|
46
server/src/modules/auth/controllers/user.controller.ts
Normal file
46
server/src/modules/auth/controllers/user.controller.ts
Normal file
@ -0,0 +1,46 @@
|
|||||||
|
import { Controller, Get, Query, HttpCode, UseGuards } from '@nestjs/common';
|
||||||
|
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
|
||||||
|
import { UserService } from '../services/user.service';
|
||||||
|
import { GetUserListDto } from '../dto/getUserList.dto';
|
||||||
|
|
||||||
|
@ApiTags('user')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller('/api/user')
|
||||||
|
export class UserController {
|
||||||
|
constructor(private readonly userService: UserService) {}
|
||||||
|
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
@Get('/getUserList')
|
||||||
|
@HttpCode(200)
|
||||||
|
async getUserList(
|
||||||
|
@Query()
|
||||||
|
queryInfo: GetUserListDto,
|
||||||
|
) {
|
||||||
|
const { value, error } = GetUserListDto.validate(queryInfo);
|
||||||
|
if (error) {
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const userList = await this.userService.getUserListByUsername({
|
||||||
|
username: value.username,
|
||||||
|
skip: (value.pageIndex - 1) * value.pageSize,
|
||||||
|
take: value.pageSize,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: userList.map((item) => {
|
||||||
|
return {
|
||||||
|
userId: item._id.toString(),
|
||||||
|
username: item.username,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
21
server/src/modules/auth/dto/getUserList.dto.ts
Normal file
21
server/src/modules/auth/dto/getUserList.dto.ts
Normal file
@ -0,0 +1,21 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
|
||||||
|
export class GetUserListDto {
|
||||||
|
@ApiProperty({ description: '用户名', required: true })
|
||||||
|
username: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '页码', required: false, default: 1 })
|
||||||
|
pageIndex?: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '每页查询数', required: false, default: 10 })
|
||||||
|
pageSize: number;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
username: Joi.string().required(),
|
||||||
|
pageIndex: Joi.number().allow(null).default(1),
|
||||||
|
pageSize: Joi.number().allow(null).default(10),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
@ -5,6 +5,8 @@ import { User } from 'src/models/user.entity';
|
|||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { hash256 } from 'src/utils/hash256';
|
import { hash256 } from 'src/utils/hash256';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
@Injectable()
|
@Injectable()
|
||||||
export class UserService {
|
export class UserService {
|
||||||
@ -51,9 +53,55 @@ export class UserService {
|
|||||||
const user = await this.userRepository.findOne({
|
const user = await this.userRepository.findOne({
|
||||||
where: {
|
where: {
|
||||||
username: username,
|
username: username,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
return user;
|
return user;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async getUserById(id: string) {
|
||||||
|
const user = await this.userRepository.findOne({
|
||||||
|
where: {
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
return user;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserListByUsername({ username, skip, take }) {
|
||||||
|
const list = await this.userRepository.find({
|
||||||
|
where: {
|
||||||
|
username: new RegExp(username),
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
skip,
|
||||||
|
take,
|
||||||
|
select: ['_id', 'username', 'createDate'],
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getUserListByIds({ idList }) {
|
||||||
|
const list = await this.userRepository.find({
|
||||||
|
where: {
|
||||||
|
_id: {
|
||||||
|
$in: idList.map((item) => new ObjectId(item)),
|
||||||
|
},
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: ['_id', 'username', 'createDate'],
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -8,14 +8,16 @@ import {
|
|||||||
Body,
|
Body,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
import { FileInterceptor } from '@nestjs/platform-express';
|
import { FileInterceptor } from '@nestjs/platform-express';
|
||||||
|
import { ConfigService } from '@nestjs/config';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { FileService } from '../services/file.service';
|
import { FileService } from '../services/file.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 { AuthService } from 'src/modules/auth/services/auth.service';
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
import { AuthenticationException } from 'src/exceptions/authException';
|
import { AuthenticationException } from 'src/exceptions/authException';
|
||||||
import { ConfigService } from '@nestjs/config';
|
|
||||||
|
|
||||||
|
@ApiTags('file')
|
||||||
@Controller('/api/file')
|
@Controller('/api/file')
|
||||||
export class FileController {
|
export class FileController {
|
||||||
constructor(
|
constructor(
|
||||||
|
@ -10,7 +10,7 @@ import {
|
|||||||
MESSAGE_PUSHING_TYPE,
|
MESSAGE_PUSHING_TYPE,
|
||||||
} from 'src/enums/messagePushing';
|
} from 'src/enums/messagePushing';
|
||||||
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
import { MessagePushingTask } from 'src/models/messagePushingTask.entity';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
|
||||||
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
import { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||||
@ -38,7 +38,7 @@ describe('MessagePushingTaskController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
provide: Authtication,
|
provide: Authentication,
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
canActivate: () => true,
|
canActivate: () => true,
|
||||||
})),
|
})),
|
||||||
|
@ -25,9 +25,9 @@ import { QueryMessagePushingTaskListDto } from '../dto/queryMessagePushingTaskLi
|
|||||||
|
|
||||||
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 { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
@UseGuards(Authentication)
|
||||||
@ApiBearerAuth()
|
@ApiBearerAuth()
|
||||||
@ApiTags('messagePushingTasks')
|
@ApiTags('messagePushingTasks')
|
||||||
@Controller('/api/messagePushingTasks')
|
@Controller('/api/messagePushingTasks')
|
||||||
@ -47,12 +47,10 @@ export class MessagePushingTaskController {
|
|||||||
req,
|
req,
|
||||||
@Body() createMessagePushingTaskDto: CreateMessagePushingTaskDto,
|
@Body() createMessagePushingTaskDto: CreateMessagePushingTaskDto,
|
||||||
) {
|
) {
|
||||||
let data;
|
const { error, value } = CreateMessagePushingTaskDto.validate(
|
||||||
try {
|
createMessagePushingTaskDto,
|
||||||
data = await CreateMessagePushingTaskDto.validate(
|
);
|
||||||
createMessagePushingTaskDto,
|
if (error) {
|
||||||
);
|
|
||||||
} catch (error) {
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
`参数错误: ${error.message}`,
|
`参数错误: ${error.message}`,
|
||||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
@ -61,7 +59,7 @@ export class MessagePushingTaskController {
|
|||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
|
|
||||||
const messagePushingTask = await this.messagePushingTaskService.create({
|
const messagePushingTask = await this.messagePushingTaskService.create({
|
||||||
...data,
|
...value,
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
@ -83,10 +81,8 @@ export class MessagePushingTaskController {
|
|||||||
req,
|
req,
|
||||||
@Query() query: QueryMessagePushingTaskListDto,
|
@Query() query: QueryMessagePushingTaskListDto,
|
||||||
) {
|
) {
|
||||||
let data;
|
const { error, value } = QueryMessagePushingTaskListDto.validate(query);
|
||||||
try {
|
if (error) {
|
||||||
data = await QueryMessagePushingTaskListDto.validate(query);
|
|
||||||
} catch (error) {
|
|
||||||
throw new HttpException(
|
throw new HttpException(
|
||||||
`参数错误: ${error.message}`,
|
`参数错误: ${error.message}`,
|
||||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
@ -94,8 +90,8 @@ export class MessagePushingTaskController {
|
|||||||
}
|
}
|
||||||
const userId = req.user._id;
|
const userId = req.user._id;
|
||||||
const list = await this.messagePushingTaskService.findAll({
|
const list = await this.messagePushingTaskService.findAll({
|
||||||
surveyId: data.surveyId,
|
surveyId: value.surveyId,
|
||||||
hook: data.triggerHook,
|
hook: value.triggerHook,
|
||||||
ownerId: userId,
|
ownerId: userId,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
|
@ -29,8 +29,8 @@ export class CreateMessagePushingTaskDto {
|
|||||||
})
|
})
|
||||||
surveys?: string[];
|
surveys?: string[];
|
||||||
|
|
||||||
static async validate(data) {
|
static validate(data) {
|
||||||
return await Joi.object({
|
return Joi.object({
|
||||||
name: Joi.string().required(),
|
name: Joi.string().required(),
|
||||||
type: Joi.string().allow(null).default(MESSAGE_PUSHING_TYPE.HTTP),
|
type: Joi.string().allow(null).default(MESSAGE_PUSHING_TYPE.HTTP),
|
||||||
pushAddress: Joi.string().required(),
|
pushAddress: Joi.string().required(),
|
||||||
@ -38,6 +38,6 @@ export class CreateMessagePushingTaskDto {
|
|||||||
.allow(null)
|
.allow(null)
|
||||||
.default(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED),
|
.default(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED),
|
||||||
surveys: Joi.array().items(Joi.string()).allow(null).default([]),
|
surveys: Joi.array().items(Joi.string()).allow(null).default([]),
|
||||||
}).validateAsync(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,6 +13,6 @@ export class QueryMessagePushingTaskListDto {
|
|||||||
return Joi.object({
|
return Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
triggerHook: Joi.string().required(),
|
triggerHook: Joi.string().required(),
|
||||||
}).validateAsync(data);
|
}).validate(data);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
220
server/src/modules/survey/__test/collaborator.controller.spec.ts
Normal file
220
server/src/modules/survey/__test/collaborator.controller.spec.ts
Normal file
@ -0,0 +1,220 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { CollaboratorController } from '../controllers/collaborator.controller';
|
||||||
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { CreateCollaboratorDto } from '../dto/createCollaborator.dto';
|
||||||
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
|
import { GetSurveyCollaboratorListDto } from '../dto/getSurveyCollaboratorList.dto';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
|
describe('CollaboratorController', () => {
|
||||||
|
let controller: CollaboratorController;
|
||||||
|
let collaboratorService: CollaboratorService;
|
||||||
|
let logger: Logger;
|
||||||
|
let userService: UserService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [CollaboratorController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: CollaboratorService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
getSurveyCollaboratorList: jest.fn(),
|
||||||
|
changeUserPermission: jest.fn(),
|
||||||
|
deleteCollaborator: jest.fn(),
|
||||||
|
getCollaborator: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useValue: {
|
||||||
|
getUserById: jest.fn().mockImplementation((id) => {
|
||||||
|
return Promise.resolve({
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
getUserListByIds: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SurveyMetaService,
|
||||||
|
useValue: {
|
||||||
|
getSurveyById: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {
|
||||||
|
findOne: jest.fn().mockResolvedValue(null),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<CollaboratorController>(CollaboratorController);
|
||||||
|
collaboratorService = module.get<CollaboratorService>(CollaboratorService);
|
||||||
|
logger = module.get<Logger>(Logger);
|
||||||
|
userService = module.get<UserService>(UserService);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should be defined', () => {
|
||||||
|
expect(controller).toBeDefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('addCollaborator', () => {
|
||||||
|
it('should add a collaborator successfully', async () => {
|
||||||
|
const userId = new ObjectId().toString();
|
||||||
|
const reqBody: CreateCollaboratorDto = {
|
||||||
|
surveyId: 'surveyId',
|
||||||
|
userId: new ObjectId().toString(),
|
||||||
|
permissions: [SURVEY_PERMISSION.SURVEY_CONF_MANAGE],
|
||||||
|
};
|
||||||
|
const req = { user: { _id: 'userId' }, surveyMeta: { ownerId: userId } };
|
||||||
|
const result = { _id: 'collaboratorId' };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(collaboratorService, 'create')
|
||||||
|
.mockResolvedValue(result as unknown as Collaborator);
|
||||||
|
|
||||||
|
const response = await controller.addCollaborator(reqBody, req);
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
collaboratorId: result._id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const reqBody: CreateCollaboratorDto = {
|
||||||
|
surveyId: '',
|
||||||
|
userId: '',
|
||||||
|
permissions: [SURVEY_PERMISSION.SURVEY_CONF_MANAGE],
|
||||||
|
};
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
|
||||||
|
await expect(controller.addCollaborator(reqBody, req)).rejects.toThrow(
|
||||||
|
HttpException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSurveyCollaboratorList', () => {
|
||||||
|
it('should return collaborator list', async () => {
|
||||||
|
const query = { surveyId: 'surveyId' };
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
const result = [
|
||||||
|
{ _id: 'collaboratorId', userId: 'userId', username: '' },
|
||||||
|
];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(collaboratorService, 'getSurveyCollaboratorList')
|
||||||
|
.mockResolvedValue(result as unknown as Array<Collaborator>);
|
||||||
|
|
||||||
|
jest.spyOn(userService, 'getUserListByIds').mockResolvedValueOnce([]);
|
||||||
|
|
||||||
|
const response = await controller.getSurveyCollaboratorList(query, req);
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const query: GetSurveyCollaboratorListDto = {
|
||||||
|
surveyId: '',
|
||||||
|
};
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.getSurveyCollaboratorList(query, req),
|
||||||
|
).rejects.toThrow(HttpException);
|
||||||
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('changeUserPermission', () => {
|
||||||
|
it('should change user permission successfully', async () => {
|
||||||
|
const reqBody = {
|
||||||
|
surveyId: 'surveyId',
|
||||||
|
userId: 'userId',
|
||||||
|
permissions: ['read'],
|
||||||
|
};
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
const result = { _id: 'userId', permissions: ['read'] };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(collaboratorService, 'changeUserPermission')
|
||||||
|
.mockResolvedValue(result);
|
||||||
|
|
||||||
|
const response = await controller.changeUserPermission(reqBody, req);
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const reqBody = {
|
||||||
|
surveyId: '',
|
||||||
|
userId: '',
|
||||||
|
permissions: ['surveyManage'],
|
||||||
|
};
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
|
||||||
|
await expect(
|
||||||
|
controller.changeUserPermission(reqBody, req),
|
||||||
|
).rejects.toThrow(HttpException);
|
||||||
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteCollaborator', () => {
|
||||||
|
it('should delete collaborator successfully', async () => {
|
||||||
|
const query = { surveyId: 'surveyId', userId: 'userId' };
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
const result = { acknowledged: true, deletedCount: 1 };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(collaboratorService, 'deleteCollaborator')
|
||||||
|
.mockResolvedValue(result);
|
||||||
|
|
||||||
|
const response = await controller.deleteCollaborator(query, req);
|
||||||
|
|
||||||
|
expect(response).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: result,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const query = { surveyId: '', userId: '' };
|
||||||
|
const req = { user: { _id: 'userId' } };
|
||||||
|
|
||||||
|
await expect(controller.deleteCollaborator(query, req)).rejects.toThrow(
|
||||||
|
HttpException,
|
||||||
|
);
|
||||||
|
expect(logger.error).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
331
server/src/modules/survey/__test/collaborator.service.spec.ts
Normal file
331
server/src/modules/survey/__test/collaborator.service.spec.ts
Normal file
@ -0,0 +1,331 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { InsertManyResult, ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
describe('CollaboratorService', () => {
|
||||||
|
let service: CollaboratorService;
|
||||||
|
let repository: MongoRepository<Collaborator>;
|
||||||
|
let logger: Logger;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
CollaboratorService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Collaborator),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
info: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<CollaboratorService>(CollaboratorService);
|
||||||
|
repository = module.get<MongoRepository<Collaborator>>(
|
||||||
|
getRepositoryToken(Collaborator),
|
||||||
|
);
|
||||||
|
logger = module.get<Logger>(Logger);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create and save a collaborator', async () => {
|
||||||
|
const createSpy = jest.spyOn(repository, 'create').mockReturnValue({
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
} as Collaborator);
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const saveSpy = jest.spyOn(repository, 'save').mockResolvedValue({
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
} as Collaborator);
|
||||||
|
|
||||||
|
const result = await service.create({
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(createSpy).toHaveBeenCalledWith({
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
expect(saveSpy).toHaveBeenCalledWith({
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchCreate', () => {
|
||||||
|
it('should batch create collaborators', async () => {
|
||||||
|
const insertManySpy = jest
|
||||||
|
.spyOn(repository, 'insertMany')
|
||||||
|
.mockResolvedValue({
|
||||||
|
insertedCount: 1,
|
||||||
|
} as unknown as InsertManyResult<Document>);
|
||||||
|
|
||||||
|
const result = await service.batchCreate({
|
||||||
|
surveyId: '1',
|
||||||
|
collaboratorList: [{ userId: '1', permissions: [] }],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(insertManySpy).toHaveBeenCalledWith([
|
||||||
|
{ surveyId: '1', userId: '1', permissions: [] },
|
||||||
|
]);
|
||||||
|
expect(result).toEqual({ insertedCount: 1 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getSurveyCollaboratorList', () => {
|
||||||
|
it('should return a list of collaborators for a survey', async () => {
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const findSpy = jest.spyOn(repository, 'find').mockResolvedValue([
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
] as Collaborator[]);
|
||||||
|
|
||||||
|
const result = await service.getSurveyCollaboratorList({ surveyId: '1' });
|
||||||
|
|
||||||
|
expect(findSpy).toHaveBeenCalledWith({ surveyId: '1' });
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCollaboratorListByIds', () => {
|
||||||
|
it('should return a list of collaborators by ids', async () => {
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const findSpy = jest.spyOn(repository, 'find').mockResolvedValue([
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
] as Collaborator[]);
|
||||||
|
|
||||||
|
const result = await service.getCollaboratorListByIds({
|
||||||
|
idList: [collaboratorId],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findSpy).toHaveBeenCalledWith({
|
||||||
|
_id: {
|
||||||
|
$in: [new ObjectId(collaboratorId)],
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result).toEqual([
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCollaborator', () => {
|
||||||
|
it('should return a collaborator', async () => {
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const findOneSpy = jest.spyOn(repository, 'findOne').mockResolvedValue({
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
} as Collaborator);
|
||||||
|
|
||||||
|
const result = await service.getCollaborator({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(findOneSpy).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result).toEqual({
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('changeUserPermission', () => {
|
||||||
|
it("should update a user's permissions", async () => {
|
||||||
|
const updateOneSpy = jest
|
||||||
|
.spyOn(repository, 'updateOne')
|
||||||
|
.mockResolvedValue({});
|
||||||
|
|
||||||
|
const result = await service.changeUserPermission({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
permission: 'read',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updateOneSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
surveyId: '1',
|
||||||
|
userId: '1',
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
permission: 'read',
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteCollaborator', () => {
|
||||||
|
it('should delete a collaborator', async () => {
|
||||||
|
const mockResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
const deleteOneSpy = jest
|
||||||
|
.spyOn(repository, 'deleteOne')
|
||||||
|
.mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const result = await service.deleteCollaborator({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(deleteOneSpy).toHaveBeenCalledWith({
|
||||||
|
userId: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchDelete', () => {
|
||||||
|
it('should batch delete collaborators', async () => {
|
||||||
|
const mockResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
const deleteManySpy = jest
|
||||||
|
.spyOn(repository, 'deleteMany')
|
||||||
|
.mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
|
||||||
|
const result = await service.batchDelete({
|
||||||
|
surveyId: '1',
|
||||||
|
idList: [collaboratorId],
|
||||||
|
});
|
||||||
|
|
||||||
|
const expectedQuery = {
|
||||||
|
surveyId: '1',
|
||||||
|
$or: [
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: [new ObjectId(collaboratorId)],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
};
|
||||||
|
|
||||||
|
expect(logger.info).toHaveBeenCalledWith(JSON.stringify(expectedQuery));
|
||||||
|
expect(deleteManySpy).toHaveBeenCalledWith(expectedQuery);
|
||||||
|
expect(result).toEqual(mockResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchDeleteBySurveyId', () => {
|
||||||
|
it('should batch delete collaborators by survey id', async () => {
|
||||||
|
const mockResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
const deleteManySpy = jest
|
||||||
|
.spyOn(repository, 'deleteMany')
|
||||||
|
.mockResolvedValue(mockResult);
|
||||||
|
|
||||||
|
const surveyId = new ObjectId().toString();
|
||||||
|
|
||||||
|
const result = await service.batchDeleteBySurveyId(surveyId);
|
||||||
|
|
||||||
|
expect(deleteManySpy).toHaveBeenCalledWith({
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
expect(result).toEqual(mockResult);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateById', () => {
|
||||||
|
it('should update collaborator by id', async () => {
|
||||||
|
const updateOneSpy = jest
|
||||||
|
.spyOn(repository, 'updateOne')
|
||||||
|
.mockResolvedValue({});
|
||||||
|
const collaboratorId = new ObjectId().toString();
|
||||||
|
const result = await service.updateById({
|
||||||
|
collaboratorId,
|
||||||
|
permissions: [],
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(updateOneSpy).toHaveBeenCalledWith(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
permissions: [],
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
expect(result).toEqual({});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('getCollaboratorListByUserId', () => {
|
||||||
|
it('should return a list of collaborators by user id', async () => {
|
||||||
|
const userId = new ObjectId().toString();
|
||||||
|
const findSpy = jest.spyOn(repository, 'find').mockResolvedValue([
|
||||||
|
{
|
||||||
|
_id: '1',
|
||||||
|
surveyId: '1',
|
||||||
|
userId: userId,
|
||||||
|
permissions: [],
|
||||||
|
} as unknown as Collaborator,
|
||||||
|
]);
|
||||||
|
|
||||||
|
const result = await service.getCollaboratorListByUserId({ userId });
|
||||||
|
|
||||||
|
expect(findSpy).toHaveBeenCalledWith({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(result).toEqual([
|
||||||
|
{ _id: '1', surveyId: '1', userId, permissions: [] },
|
||||||
|
]);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -9,7 +9,8 @@ import { ResponseSchemaService } from '../../surveyResponse/services/responseSch
|
|||||||
|
|
||||||
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 { Logger } from 'src/logger';
|
||||||
|
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
@ -18,10 +19,13 @@ 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');
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
describe('DataStatisticController', () => {
|
describe('DataStatisticController', () => {
|
||||||
let controller: DataStatisticController;
|
let controller: DataStatisticController;
|
||||||
let dataStatisticService: DataStatisticService;
|
let dataStatisticService: DataStatisticService;
|
||||||
let surveyMetaService: SurveyMetaService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -32,12 +36,6 @@ describe('DataStatisticController', () => {
|
|||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
ConfigService,
|
ConfigService,
|
||||||
{
|
|
||||||
provide: Authtication,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
canActivate: () => true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: UserService,
|
provide: UserService,
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
@ -54,13 +52,18 @@ describe('DataStatisticController', () => {
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<DataStatisticController>(DataStatisticController);
|
controller = module.get<DataStatisticController>(DataStatisticController);
|
||||||
dataStatisticService =
|
dataStatisticService =
|
||||||
module.get<DataStatisticService>(DataStatisticService);
|
module.get<DataStatisticService>(DataStatisticService);
|
||||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
|
||||||
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||||
XiaojuSurveyPluginManager,
|
XiaojuSurveyPluginManager,
|
||||||
);
|
);
|
||||||
@ -101,9 +104,6 @@ describe('DataStatisticController', () => {
|
|||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValueOnce(undefined);
|
|
||||||
jest
|
jest
|
||||||
.spyOn(controller['responseSchemaService'], 'getResponseSchemaByPageId')
|
.spyOn(controller['responseSchemaService'], 'getResponseSchemaByPageId')
|
||||||
.mockResolvedValueOnce({} as any);
|
.mockResolvedValueOnce({} as any);
|
||||||
@ -111,7 +111,7 @@ describe('DataStatisticController', () => {
|
|||||||
.spyOn(dataStatisticService, 'getDataTable')
|
.spyOn(dataStatisticService, 'getDataTable')
|
||||||
.mockResolvedValueOnce(mockDataTable);
|
.mockResolvedValueOnce(mockDataTable);
|
||||||
|
|
||||||
const result = await controller.data(mockRequest.query, mockRequest);
|
const result = await controller.data(mockRequest.query, {});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -146,10 +146,6 @@ describe('DataStatisticController', () => {
|
|||||||
{ difTime: '0.5', createDate: '2024-02-11', data123: '13800000000' },
|
{ difTime: '0.5', createDate: '2024-02-11', data123: '13800000000' },
|
||||||
],
|
],
|
||||||
};
|
};
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValueOnce(undefined);
|
|
||||||
jest
|
jest
|
||||||
.spyOn(controller['responseSchemaService'], 'getResponseSchemaByPageId')
|
.spyOn(controller['responseSchemaService'], 'getResponseSchemaByPageId')
|
||||||
.mockResolvedValueOnce({} as any);
|
.mockResolvedValueOnce({} as any);
|
||||||
@ -157,7 +153,7 @@ describe('DataStatisticController', () => {
|
|||||||
.spyOn(dataStatisticService, 'getDataTable')
|
.spyOn(dataStatisticService, 'getDataTable')
|
||||||
.mockResolvedValueOnce(mockDataTable);
|
.mockResolvedValueOnce(mockDataTable);
|
||||||
|
|
||||||
const result = await controller.data(mockRequest.query, mockRequest);
|
const result = await controller.data(mockRequest.query, {});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
|
@ -18,7 +18,9 @@ jest.mock('../../surveyResponse/services/responseScheme.service');
|
|||||||
jest.mock('../services/contentSecurity.service');
|
jest.mock('../services/contentSecurity.service');
|
||||||
jest.mock('../services/surveyHistory.service');
|
jest.mock('../services/surveyHistory.service');
|
||||||
|
|
||||||
jest.mock('src/guards/authtication');
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
describe('SurveyController', () => {
|
describe('SurveyController', () => {
|
||||||
let controller: SurveyController;
|
let controller: SurveyController;
|
||||||
@ -98,7 +100,7 @@ describe('SurveyController', () => {
|
|||||||
);
|
);
|
||||||
|
|
||||||
const result = await controller.createSurvey(surveyInfo, {
|
const result = await controller.createSurvey(surveyInfo, {
|
||||||
user: { username: 'testUser' },
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -123,9 +125,6 @@ describe('SurveyController', () => {
|
|||||||
createMethod: 'copy',
|
createMethod: 'copy',
|
||||||
createFrom: existsSurveyId.toString(),
|
createFrom: existsSurveyId.toString(),
|
||||||
};
|
};
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValue(Promise.resolve(existsSurveyMeta));
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyMetaService, 'createSurveyMeta')
|
.spyOn(surveyMetaService, 'createSurveyMeta')
|
||||||
@ -136,7 +135,10 @@ describe('SurveyController', () => {
|
|||||||
return Promise.resolve(result);
|
return Promise.resolve(result);
|
||||||
});
|
});
|
||||||
|
|
||||||
const request = { user: { username: 'testUser' } }; // 模拟请求对象,根据实际情况进行调整
|
const request = {
|
||||||
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
|
surveyMeta: existsSurveyMeta,
|
||||||
|
}; // 模拟请求对象,根据实际情况进行调整
|
||||||
const result = await controller.createSurvey(params, request);
|
const result = await controller.createSurvey(params, request);
|
||||||
expect(result?.data?.id).toBeDefined();
|
expect(result?.data?.id).toBeDefined();
|
||||||
});
|
});
|
||||||
@ -151,9 +153,6 @@ describe('SurveyController', () => {
|
|||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
} as SurveyMeta;
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'saveSurveyConf')
|
.spyOn(surveyConfService, 'saveSurveyConf')
|
||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
@ -183,6 +182,7 @@ describe('SurveyController', () => {
|
|||||||
|
|
||||||
const result = await controller.updateConf(reqBody, {
|
const result = await controller.updateConf(reqBody, {
|
||||||
user: { username: 'testUser', _id: 'testUserId' },
|
user: { username: 'testUser', _id: 'testUserId' },
|
||||||
|
surveyMeta,
|
||||||
});
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -200,9 +200,6 @@ describe('SurveyController', () => {
|
|||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
} as SurveyMeta;
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyMetaService, 'deleteSurveyMeta')
|
.spyOn(surveyMetaService, 'deleteSurveyMeta')
|
||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
@ -210,10 +207,10 @@ describe('SurveyController', () => {
|
|||||||
.spyOn(responseSchemaService, 'deleteResponseSchema')
|
.spyOn(responseSchemaService, 'deleteResponseSchema')
|
||||||
.mockResolvedValue(undefined);
|
.mockResolvedValue(undefined);
|
||||||
|
|
||||||
const result = await controller.deleteSurvey(
|
const result = await controller.deleteSurvey({
|
||||||
{ surveyId: surveyId.toString() },
|
user: { username: 'testUser' },
|
||||||
{ user: { username: 'testUser' } },
|
surveyMeta,
|
||||||
);
|
});
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -230,10 +227,6 @@ describe('SurveyController', () => {
|
|||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
} as SurveyMeta;
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue(
|
.mockResolvedValue(
|
||||||
@ -243,7 +236,10 @@ describe('SurveyController', () => {
|
|||||||
} as SurveyConf),
|
} as SurveyConf),
|
||||||
);
|
);
|
||||||
|
|
||||||
const request = { user: { username: 'testUser' } };
|
const request = {
|
||||||
|
user: { username: 'testUser', _id: new ObjectId() },
|
||||||
|
surveyMeta,
|
||||||
|
};
|
||||||
const result = await controller.getSurvey(
|
const result = await controller.getSurvey(
|
||||||
{ surveyId: surveyId.toString() },
|
{ surveyId: surveyId.toString() },
|
||||||
request,
|
request,
|
||||||
@ -262,10 +258,6 @@ describe('SurveyController', () => {
|
|||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
} as SurveyMeta;
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue(
|
.mockResolvedValue(
|
||||||
@ -296,7 +288,7 @@ describe('SurveyController', () => {
|
|||||||
|
|
||||||
const result = await controller.publishSurvey(
|
const result = await controller.publishSurvey(
|
||||||
{ surveyId: surveyId.toString() },
|
{ surveyId: surveyId.toString() },
|
||||||
{ user: { username: 'testUser', _id: 'testUserId' } },
|
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
||||||
);
|
);
|
||||||
|
|
||||||
expect(result).toEqual({
|
expect(result).toEqual({
|
||||||
@ -312,10 +304,6 @@ describe('SurveyController', () => {
|
|||||||
owner: 'testUser',
|
owner: 'testUser',
|
||||||
} as SurveyMeta;
|
} as SurveyMeta;
|
||||||
|
|
||||||
jest
|
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
|
||||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
|
||||||
|
|
||||||
jest
|
jest
|
||||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||||
.mockResolvedValue(
|
.mockResolvedValue(
|
||||||
@ -338,7 +326,7 @@ describe('SurveyController', () => {
|
|||||||
await expect(
|
await expect(
|
||||||
controller.publishSurvey(
|
controller.publishSurvey(
|
||||||
{ surveyId: surveyId.toString() },
|
{ surveyId: surveyId.toString() },
|
||||||
{ user: { username: 'testUser', _id: 'testUserId' } },
|
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
||||||
),
|
),
|
||||||
).rejects.toThrow(
|
).rejects.toThrow(
|
||||||
new HttpException(
|
new HttpException(
|
||||||
|
@ -6,13 +6,16 @@ import { SurveyHistoryService } from '../services/surveyHistory.service';
|
|||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
|
||||||
import { UserService } from 'src/modules/auth/services/user.service';
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
|
||||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
describe('SurveyHistoryController', () => {
|
describe('SurveyHistoryController', () => {
|
||||||
let controller: SurveyHistoryController;
|
let controller: SurveyHistoryController;
|
||||||
let surveyHistoryService: SurveyHistoryService;
|
let surveyHistoryService: SurveyHistoryService;
|
||||||
let surveyMetaService: SurveyMetaService;
|
|
||||||
|
|
||||||
beforeEach(async () => {
|
beforeEach(async () => {
|
||||||
const module: TestingModule = await Test.createTestingModule({
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
@ -25,18 +28,6 @@ describe('SurveyHistoryController', () => {
|
|||||||
getHistoryList: jest.fn().mockResolvedValue('mockHistoryList'),
|
getHistoryList: jest.fn().mockResolvedValue('mockHistoryList'),
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
provide: SurveyMetaService,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
checkSurveyAccess: jest.fn().mockResolvedValue({}),
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
|
||||||
provide: Authtication,
|
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
|
||||||
canActivate: () => true,
|
|
||||||
})),
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
provide: UserService,
|
provide: UserService,
|
||||||
useClass: jest.fn().mockImplementation(() => ({
|
useClass: jest.fn().mockImplementation(() => ({
|
||||||
@ -53,25 +44,29 @@ describe('SurveyHistoryController', () => {
|
|||||||
},
|
},
|
||||||
})),
|
})),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: SurveyMetaService,
|
||||||
|
useClass: jest.fn().mockImplementation(() => ({})),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
controller = module.get<SurveyHistoryController>(SurveyHistoryController);
|
controller = module.get<SurveyHistoryController>(SurveyHistoryController);
|
||||||
surveyHistoryService =
|
surveyHistoryService =
|
||||||
module.get<SurveyHistoryService>(SurveyHistoryService);
|
module.get<SurveyHistoryService>(SurveyHistoryService);
|
||||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should return history list when query is valid', async () => {
|
it('should return history list when query is valid', async () => {
|
||||||
const req = { user: { username: 'testUser' } };
|
|
||||||
const queryInfo = { surveyId: 'survey123', historyType: 'published' };
|
const queryInfo = { surveyId: 'survey123', historyType: 'published' };
|
||||||
|
|
||||||
await controller.getList(queryInfo, req);
|
await controller.getList(queryInfo, {});
|
||||||
|
|
||||||
expect(surveyMetaService.checkSurveyAccess).toHaveBeenCalledWith({
|
|
||||||
surveyId: queryInfo.surveyId,
|
|
||||||
username: req.user.username,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledWith({
|
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledWith({
|
||||||
surveyId: queryInfo.surveyId,
|
surveyId: queryInfo.surveyId,
|
||||||
@ -79,6 +74,5 @@ describe('SurveyHistoryController', () => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledTimes(1);
|
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledTimes(1);
|
||||||
expect(surveyMetaService.checkSurveyAccess).toHaveBeenCalledTimes(1);
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { Test, TestingModule } from '@nestjs/testing';
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
|
||||||
import { LoggerProvider } from 'src/logger/logger.provider';
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
describe('SurveyMetaController', () => {
|
describe('SurveyMetaController', () => {
|
||||||
let controller: SurveyMetaController;
|
let controller: SurveyMetaController;
|
||||||
@ -18,7 +22,6 @@ describe('SurveyMetaController', () => {
|
|||||||
{
|
{
|
||||||
provide: SurveyMetaService,
|
provide: SurveyMetaService,
|
||||||
useValue: {
|
useValue: {
|
||||||
checkSurveyAccess: jest.fn().mockResolvedValue({}),
|
|
||||||
editSurveyMeta: jest.fn().mockResolvedValue(undefined),
|
editSurveyMeta: jest.fn().mockResolvedValue(undefined),
|
||||||
getSurveyMetaList: jest
|
getSurveyMetaList: jest
|
||||||
.fn()
|
.fn()
|
||||||
@ -26,13 +29,14 @@ describe('SurveyMetaController', () => {
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
LoggerProvider,
|
LoggerProvider,
|
||||||
|
{
|
||||||
|
provide: CollaboratorService,
|
||||||
|
useValue: {
|
||||||
|
getCollaboratorListByUserId: jest.fn().mockResolvedValue([]),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
}).compile();
|
||||||
.overrideGuard(Authtication)
|
|
||||||
.useValue({
|
|
||||||
canActivate: () => true,
|
|
||||||
})
|
|
||||||
.compile();
|
|
||||||
|
|
||||||
controller = module.get<SurveyMetaController>(SurveyMetaController);
|
controller = module.get<SurveyMetaController>(SurveyMetaController);
|
||||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
||||||
@ -44,30 +48,21 @@ describe('SurveyMetaController', () => {
|
|||||||
title: 'Test title',
|
title: 'Test title',
|
||||||
surveyId: 'test-survey-id',
|
surveyId: 'test-survey-id',
|
||||||
};
|
};
|
||||||
const req = {
|
|
||||||
user: {
|
|
||||||
username: 'test-user',
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
const survey = {
|
const survey = {
|
||||||
title: '',
|
title: '',
|
||||||
remark: '',
|
remark: '',
|
||||||
};
|
};
|
||||||
|
|
||||||
jest
|
const req = {
|
||||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
user: {
|
||||||
.mockImplementation(() => {
|
username: 'test-user',
|
||||||
return Promise.resolve(survey) as Promise<SurveyMeta>;
|
},
|
||||||
});
|
surveyMeta: survey,
|
||||||
|
};
|
||||||
|
|
||||||
const result = await controller.updateMeta(reqBody, req);
|
const result = await controller.updateMeta(reqBody, req);
|
||||||
|
|
||||||
expect(surveyMetaService.checkSurveyAccess).toHaveBeenCalledWith({
|
|
||||||
surveyId: reqBody.surveyId,
|
|
||||||
username: req.user.username,
|
|
||||||
});
|
|
||||||
|
|
||||||
expect(surveyMetaService.editSurveyMeta).toHaveBeenCalledWith({
|
expect(surveyMetaService.editSurveyMeta).toHaveBeenCalledWith({
|
||||||
title: reqBody.title,
|
title: reqBody.title,
|
||||||
remark: reqBody.remark,
|
remark: reqBody.remark,
|
||||||
@ -91,7 +86,6 @@ describe('SurveyMetaController', () => {
|
|||||||
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
expect(surveyMetaService.checkSurveyAccess).not.toHaveBeenCalled();
|
|
||||||
expect(surveyMetaService.editSurveyMeta).not.toHaveBeenCalled();
|
expect(surveyMetaService.editSurveyMeta).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
@ -100,65 +94,66 @@ describe('SurveyMetaController', () => {
|
|||||||
curPage: 1,
|
curPage: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
};
|
};
|
||||||
|
const userId = new ObjectId().toString();
|
||||||
const req = {
|
const req = {
|
||||||
user: {
|
user: {
|
||||||
username: 'test-user',
|
username: 'test-user',
|
||||||
|
_id: new ObjectId(userId),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
jest
|
||||||
jest
|
.spyOn(surveyMetaService, 'getSurveyMetaList')
|
||||||
.spyOn(surveyMetaService, 'getSurveyMetaList')
|
.mockImplementation(() => {
|
||||||
.mockImplementation(() => {
|
const date = new Date().getTime();
|
||||||
const date = new Date().getTime();
|
return Promise.resolve({
|
||||||
return Promise.resolve({
|
|
||||||
count: 10,
|
|
||||||
data: [
|
|
||||||
{
|
|
||||||
id: '1',
|
|
||||||
createDate: date,
|
|
||||||
updateDate: date,
|
|
||||||
curStatus: {
|
|
||||||
date: date,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
],
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
const result = await controller.getList(queryInfo, req);
|
|
||||||
|
|
||||||
expect(result).toEqual({
|
|
||||||
code: 200,
|
|
||||||
data: {
|
|
||||||
count: 10,
|
count: 10,
|
||||||
data: expect.arrayContaining([
|
data: [
|
||||||
expect.objectContaining({
|
{
|
||||||
createDate: expect.stringMatching(
|
_id: new ObjectId(),
|
||||||
|
createDate: date,
|
||||||
|
updateDate: date,
|
||||||
|
curStatus: {
|
||||||
|
date: date,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
const result = await controller.getList(queryInfo, req);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
count: 10,
|
||||||
|
data: expect.arrayContaining([
|
||||||
|
expect.objectContaining({
|
||||||
|
createDate: expect.stringMatching(
|
||||||
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
|
),
|
||||||
|
updateDate: expect.stringMatching(
|
||||||
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
|
),
|
||||||
|
curStatus: expect.objectContaining({
|
||||||
|
date: expect.stringMatching(
|
||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
||||||
),
|
),
|
||||||
updateDate: expect.stringMatching(
|
|
||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
|
||||||
),
|
|
||||||
curStatus: expect.objectContaining({
|
|
||||||
date: expect.stringMatching(
|
|
||||||
/^\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2}$/,
|
|
||||||
),
|
|
||||||
}),
|
|
||||||
}),
|
}),
|
||||||
]),
|
}),
|
||||||
},
|
]),
|
||||||
});
|
},
|
||||||
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
});
|
||||||
pageNum: queryInfo.curPage,
|
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
||||||
pageSize: queryInfo.pageSize,
|
pageNum: queryInfo.curPage,
|
||||||
username: req.user.username,
|
pageSize: queryInfo.pageSize,
|
||||||
filter: {},
|
username: req.user.username,
|
||||||
order: {},
|
filter: {},
|
||||||
});
|
order: {},
|
||||||
} catch (error) {
|
surveyIdList: [],
|
||||||
console.log(error);
|
userId,
|
||||||
}
|
workspaceId: undefined,
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should get survey meta list with filter and order', async () => {
|
it('should get survey meta list with filter and order', async () => {
|
||||||
@ -177,25 +172,26 @@ describe('SurveyMetaController', () => {
|
|||||||
]),
|
]),
|
||||||
order: JSON.stringify([{ field: 'createDate', value: -1 }]),
|
order: JSON.stringify([{ field: 'createDate', value: -1 }]),
|
||||||
};
|
};
|
||||||
|
const userId = new ObjectId().toString();
|
||||||
const req = {
|
const req = {
|
||||||
user: {
|
user: {
|
||||||
username: 'test-user',
|
username: 'test-user',
|
||||||
|
_id: new ObjectId(userId),
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
try {
|
const result = await controller.getList(queryInfo, req);
|
||||||
const result = await controller.getList(queryInfo, req);
|
|
||||||
|
|
||||||
expect(result.code).toEqual(200);
|
expect(result.code).toEqual(200);
|
||||||
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
expect(surveyMetaService.getSurveyMetaList).toHaveBeenCalledWith({
|
||||||
pageNum: queryInfo.curPage,
|
pageNum: queryInfo.curPage,
|
||||||
pageSize: queryInfo.pageSize,
|
pageSize: queryInfo.pageSize,
|
||||||
username: req.user.username,
|
username: req.user.username,
|
||||||
filter: { surveyType: 'normal', title: { $regex: 'hahah' } },
|
surveyIdList: [],
|
||||||
order: { createDate: -1 },
|
userId,
|
||||||
});
|
filter: { surveyType: 'normal', title: { $regex: 'hahah' } },
|
||||||
} catch (error) {
|
order: { createDate: -1 },
|
||||||
console.log(error);
|
workspaceId: undefined,
|
||||||
}
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -2,15 +2,13 @@ import { Test, TestingModule } from '@nestjs/testing';
|
|||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
import { MongoRepository } from 'typeorm';
|
import { MongoRepository } from 'typeorm';
|
||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { ObjectId } from 'mongodb';
|
|
||||||
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 { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
|
||||||
import { NoSurveyPermissionException } from 'src/exceptions/noSurveyPermissionException';
|
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { getRepositoryToken } from '@nestjs/typeorm';
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { SurveyUtilPlugin } from 'src/securityPlugin/surveyUtilPlugin';
|
import { SurveyUtilPlugin } from 'src/securityPlugin/surveyUtilPlugin';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
describe('SurveyMetaService', () => {
|
describe('SurveyMetaService', () => {
|
||||||
let service: SurveyMetaService;
|
let service: SurveyMetaService;
|
||||||
@ -57,52 +55,6 @@ describe('SurveyMetaService', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('checkSurveyAccess', () => {
|
|
||||||
it('should return survey when user has access', async () => {
|
|
||||||
const surveyId = new ObjectId().toHexString();
|
|
||||||
const username = 'testUser';
|
|
||||||
const survey = { owner: username } as SurveyMeta;
|
|
||||||
jest.spyOn(surveyRepository, 'findOne').mockResolvedValue(survey);
|
|
||||||
|
|
||||||
const result = await service.checkSurveyAccess({ surveyId, username });
|
|
||||||
|
|
||||||
expect(result).toBe(survey);
|
|
||||||
expect(surveyRepository.findOne).toHaveBeenCalledWith({
|
|
||||||
where: { _id: new ObjectId(surveyId) },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw SurveyNotFoundException when survey not found', async () => {
|
|
||||||
const surveyId = new ObjectId().toHexString();
|
|
||||||
const username = 'testUser';
|
|
||||||
jest.spyOn(surveyRepository, 'findOne').mockResolvedValue(null);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
service.checkSurveyAccess({ surveyId, username }),
|
|
||||||
).rejects.toThrow(SurveyNotFoundException);
|
|
||||||
|
|
||||||
expect(surveyRepository.findOne).toHaveBeenCalledWith({
|
|
||||||
where: { _id: new ObjectId(surveyId) },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
it('should throw NoSurveyPermissionException when user has no access', async () => {
|
|
||||||
const surveyId = new ObjectId().toHexString();
|
|
||||||
const username = 'testUser';
|
|
||||||
const surveyOwner = 'otherUser';
|
|
||||||
const survey = { owner: surveyOwner } as SurveyMeta;
|
|
||||||
jest.spyOn(surveyRepository, 'findOne').mockResolvedValue(survey);
|
|
||||||
|
|
||||||
await expect(
|
|
||||||
service.checkSurveyAccess({ surveyId, username }),
|
|
||||||
).rejects.toThrow(NoSurveyPermissionException);
|
|
||||||
|
|
||||||
expect(surveyRepository.findOne).toHaveBeenCalledWith({
|
|
||||||
where: { _id: new ObjectId(surveyId) },
|
|
||||||
});
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
describe('createSurveyMeta', () => {
|
describe('createSurveyMeta', () => {
|
||||||
it('should create a new survey meta and return it', async () => {
|
it('should create a new survey meta and return it', async () => {
|
||||||
const params = {
|
const params = {
|
||||||
@ -110,6 +62,7 @@ describe('SurveyMetaService', () => {
|
|||||||
remark: 'This is a test survey',
|
remark: 'This is a test survey',
|
||||||
surveyType: 'normal',
|
surveyType: 'normal',
|
||||||
username: 'testUser',
|
username: 'testUser',
|
||||||
|
userId: new ObjectId().toString(),
|
||||||
createMethod: '',
|
createMethod: '',
|
||||||
createFrom: '',
|
createFrom: '',
|
||||||
};
|
};
|
||||||
@ -133,6 +86,7 @@ describe('SurveyMetaService', () => {
|
|||||||
surveyType: params.surveyType,
|
surveyType: params.surveyType,
|
||||||
surveyPath: mockedSurveyPath,
|
surveyPath: mockedSurveyPath,
|
||||||
creator: params.username,
|
creator: params.username,
|
||||||
|
ownerId: params.userId,
|
||||||
owner: params.username,
|
owner: params.username,
|
||||||
createMethod: params.createMethod,
|
createMethod: params.createMethod,
|
||||||
createFrom: params.createFrom,
|
createFrom: params.createFrom,
|
||||||
@ -213,6 +167,7 @@ describe('SurveyMetaService', () => {
|
|||||||
const condition = {
|
const condition = {
|
||||||
pageNum: 1,
|
pageNum: 1,
|
||||||
pageSize: 10,
|
pageSize: 10,
|
||||||
|
userId: 'testUserId',
|
||||||
username: 'testUser',
|
username: 'testUser',
|
||||||
filter: {},
|
filter: {},
|
||||||
order: {},
|
order: {},
|
||||||
@ -222,15 +177,7 @@ describe('SurveyMetaService', () => {
|
|||||||
// 验证返回值
|
// 验证返回值
|
||||||
expect(result).toEqual({ data: mockData, count: mockCount });
|
expect(result).toEqual({ data: mockData, count: mockCount });
|
||||||
// 验证repository方法被正确调用
|
// 验证repository方法被正确调用
|
||||||
expect(surveyRepository.findAndCount).toHaveBeenCalledWith({
|
expect(surveyRepository.findAndCount).toHaveBeenCalledTimes(1);
|
||||||
where: {
|
|
||||||
owner: 'testUser',
|
|
||||||
'curStatus.status': { $ne: 'removed' },
|
|
||||||
},
|
|
||||||
skip: 0,
|
|
||||||
take: 10,
|
|
||||||
order: { createDate: -1 },
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
375
server/src/modules/survey/controllers/collaborator.controller.ts
Normal file
375
server/src/modules/survey/controllers/collaborator.controller.ts
Normal file
@ -0,0 +1,375 @@
|
|||||||
|
import {
|
||||||
|
Body,
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
HttpCode,
|
||||||
|
Post,
|
||||||
|
Query,
|
||||||
|
Request,
|
||||||
|
SetMetadata,
|
||||||
|
UseGuards,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import {
|
||||||
|
SURVEY_PERMISSION,
|
||||||
|
SURVEY_PERMISSION_DESCRIPTION,
|
||||||
|
} from 'src/enums/surveyPermission';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||||
|
|
||||||
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
|
||||||
|
import { CreateCollaboratorDto } from '../dto/createCollaborator.dto';
|
||||||
|
import { ChangeUserPermissionDto } from '../dto/changeUserPermission.dto';
|
||||||
|
import { GetSurveyCollaboratorListDto } from '../dto/getSurveyCollaboratorList.dto';
|
||||||
|
import { BatchSaveCollaboratorDto } from '../dto/batchSaveCollaborator.dto';
|
||||||
|
import { splitCollaborators } from '../utils/splitCollaborator';
|
||||||
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
@ApiTags('collaborator')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@Controller('/api/collaborator')
|
||||||
|
export class CollaboratorController {
|
||||||
|
constructor(
|
||||||
|
private readonly collaboratorService: CollaboratorService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
private readonly userService: UserService,
|
||||||
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
|
private readonly workspaceMemberServie: WorkspaceMemberService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('getPermissionList')
|
||||||
|
@HttpCode(200)
|
||||||
|
async getPermissionList() {
|
||||||
|
const vals = Object.values(SURVEY_PERMISSION_DESCRIPTION);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: vals,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
])
|
||||||
|
async addCollaborator(
|
||||||
|
@Body() reqBody: CreateCollaboratorDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { error, value } = CreateCollaboratorDto.validate(reqBody);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException(
|
||||||
|
'系统错误,请联系管理员',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 检查用户是否存在
|
||||||
|
const user = await this.userService.getUserById(value.userId);
|
||||||
|
if (!user) {
|
||||||
|
throw new HttpException('用户不存在', EXCEPTION_CODE.USER_NOT_EXISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (user._id.toString() === req.surveyMeta.ownerId) {
|
||||||
|
throw new HttpException(
|
||||||
|
'不能给问卷所有者授权',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const collaborator = await this.collaboratorService.getCollaborator({
|
||||||
|
userId: value.userId,
|
||||||
|
surveyId: value.surveyId,
|
||||||
|
});
|
||||||
|
|
||||||
|
if (collaborator) {
|
||||||
|
throw new HttpException(
|
||||||
|
'用户已经是协作者',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.collaboratorService.create(value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
collaboratorId: res._id.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('batchSave')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
])
|
||||||
|
async batchSaveCollaborator(
|
||||||
|
@Body() reqBody: BatchSaveCollaboratorDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { error, value } = BatchSaveCollaboratorDto.validate(reqBody);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException(
|
||||||
|
'系统错误,请联系管理员',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value.collaborators) && value.collaborators.length > 0) {
|
||||||
|
const collaboratorUserIdList = value.collaborators.map(
|
||||||
|
(item) => item.userId,
|
||||||
|
);
|
||||||
|
for (const collaboratorUserId of collaboratorUserIdList) {
|
||||||
|
if (collaboratorUserId === req.surveyMeta.ownerId) {
|
||||||
|
throw new HttpException(
|
||||||
|
'不能给问卷所有者授权',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 不能有重复的userId
|
||||||
|
const userIdSet = new Set(collaboratorUserIdList);
|
||||||
|
if (collaboratorUserIdList.length !== Array.from(userIdSet).length) {
|
||||||
|
throw new HttpException(
|
||||||
|
'不能重复添加用户',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const userList = await this.userService.getUserListByIds({
|
||||||
|
idList: collaboratorUserIdList,
|
||||||
|
});
|
||||||
|
const userInfoMap = userList.reduce((pre, cur) => {
|
||||||
|
const id = cur._id.toString();
|
||||||
|
pre[id] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
for (const collaborator of value.collaborators) {
|
||||||
|
if (!userInfoMap[collaborator.userId]) {
|
||||||
|
throw new HttpException(
|
||||||
|
`用户id: {${collaborator.userId}} 不存在`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value.collaborators) && value.collaborators.length > 0) {
|
||||||
|
const { newCollaborator, existsCollaborator } = splitCollaborators(
|
||||||
|
value.collaborators,
|
||||||
|
);
|
||||||
|
const collaboratorIdList = existsCollaborator.map((item) => item._id);
|
||||||
|
const newCollaboratorUserIdList = newCollaborator.map(
|
||||||
|
(item) => item.userId,
|
||||||
|
);
|
||||||
|
const delRes = await this.collaboratorService.batchDelete({
|
||||||
|
surveyId: value.surveyId,
|
||||||
|
idList: [],
|
||||||
|
neIdList: collaboratorIdList,
|
||||||
|
userIdList: newCollaboratorUserIdList,
|
||||||
|
});
|
||||||
|
this.logger.info('batchDelete:' + JSON.stringify(delRes), { req });
|
||||||
|
if (Array.isArray(newCollaborator) && newCollaborator.length > 0) {
|
||||||
|
const insertRes = await this.collaboratorService.batchCreate({
|
||||||
|
surveyId: value.surveyId,
|
||||||
|
collaboratorList: newCollaborator,
|
||||||
|
});
|
||||||
|
this.logger.info(`${JSON.stringify(insertRes)}`);
|
||||||
|
}
|
||||||
|
if (Array.isArray(existsCollaborator) && existsCollaborator.length > 0) {
|
||||||
|
const updateRes = await Promise.all(
|
||||||
|
existsCollaborator.map((item) =>
|
||||||
|
this.collaboratorService.updateById({
|
||||||
|
collaboratorId: item._id,
|
||||||
|
permissions: item.permissions,
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
);
|
||||||
|
this.logger.info(`${JSON.stringify(updateRes)}`);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// 删除所有协作者
|
||||||
|
const delRes = await this.collaboratorService.batchDeleteBySurveyId(
|
||||||
|
value.surveyId,
|
||||||
|
);
|
||||||
|
this.logger.info(JSON.stringify(delRes), { req });
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get('')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'query.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
])
|
||||||
|
async getSurveyCollaboratorList(
|
||||||
|
@Query() query: GetSurveyCollaboratorListDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { error, value } = GetSurveyCollaboratorListDto.validate(query);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.collaboratorService.getSurveyCollaboratorList(value);
|
||||||
|
|
||||||
|
const userIdList = res.map((item) => item.userId);
|
||||||
|
const userList = await this.userService.getUserListByIds({
|
||||||
|
idList: userIdList,
|
||||||
|
});
|
||||||
|
const userInfoMap = userList.reduce((pre, cur) => {
|
||||||
|
const id = cur._id.toString();
|
||||||
|
pre[id] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: res.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
username: userInfoMap[item.userId]?.username || '',
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('changeUserPermission')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
])
|
||||||
|
async changeUserPermission(
|
||||||
|
@Body() reqBody: ChangeUserPermissionDto,
|
||||||
|
@Request() req,
|
||||||
|
) {
|
||||||
|
const { error, value } = Joi.object({
|
||||||
|
surveyId: Joi.string(),
|
||||||
|
userId: Joi.string(),
|
||||||
|
permissions: Joi.array().items(Joi.string().required()),
|
||||||
|
}).validate(reqBody);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.collaboratorService.changeUserPermission(value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: res,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('deleteCollaborator')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
])
|
||||||
|
async deleteCollaborator(@Query() query, @Request() req) {
|
||||||
|
const { error, value } = Joi.object({
|
||||||
|
surveyId: Joi.string(),
|
||||||
|
userId: Joi.string(),
|
||||||
|
}).validate(query);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
|
const res = await this.collaboratorService.deleteCollaborator(value);
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: res,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@HttpCode(200)
|
||||||
|
@Get('permissions')
|
||||||
|
async getUserSurveyPermissions(@Request() req, @Query() query) {
|
||||||
|
const user = req.user;
|
||||||
|
const userId = user._id.toString();
|
||||||
|
const surveyId = query.surveyId;
|
||||||
|
const surveyMeta = await this.surveyMetaService.getSurveyById({ surveyId });
|
||||||
|
|
||||||
|
if (!surveyMeta) {
|
||||||
|
throw new HttpException('问卷不存在', EXCEPTION_CODE.SURVEY_NOT_FOUND);
|
||||||
|
}
|
||||||
|
|
||||||
|
// 问卷owner,有问卷的权限
|
||||||
|
if (
|
||||||
|
surveyMeta?.ownerId === userId ||
|
||||||
|
surveyMeta?.owner === req.user.username
|
||||||
|
) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
isOwner: true,
|
||||||
|
permissions: [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
// 有空间权限,默认也有所有权限
|
||||||
|
if (surveyMeta.workspaceId) {
|
||||||
|
const memberInfo = await this.workspaceMemberServie.findOne({
|
||||||
|
workspaceId: surveyMeta.workspaceId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
if (memberInfo) {
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
isOwner: false,
|
||||||
|
permissions: [
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const colloborator = await this.collaboratorService.getCollaborator({
|
||||||
|
surveyId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
isOwner: false,
|
||||||
|
permissions: colloborator?.permissions || [],
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -4,49 +4,56 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
|
SetMetadata,
|
||||||
Request,
|
Request,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { DataStatisticService } from '../services/dataStatistic.service';
|
import { DataStatisticService } from '../services/dataStatistic.service';
|
||||||
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 { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
import { Authtication } from 'src/guards/authtication';
|
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
|
@ApiBearerAuth()
|
||||||
@Controller('/api/survey/dataStatistic')
|
@Controller('/api/survey/dataStatistic')
|
||||||
export class DataStatisticController {
|
export class DataStatisticController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly surveyMetaService: SurveyMetaService,
|
|
||||||
private readonly responseSchemaService: ResponseSchemaService,
|
private readonly responseSchemaService: ResponseSchemaService,
|
||||||
private readonly dataStatisticService: DataStatisticService,
|
private readonly dataStatisticService: DataStatisticService,
|
||||||
private readonly pluginManager: XiaojuSurveyPluginManager,
|
private readonly pluginManager: XiaojuSurveyPluginManager,
|
||||||
|
private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Get('/dataTable')
|
@Get('/dataTable')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'query.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
async data(
|
async data(
|
||||||
@Query()
|
@Query()
|
||||||
queryInfo,
|
queryInfo,
|
||||||
@Request()
|
@Request() req,
|
||||||
req,
|
|
||||||
) {
|
) {
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = await Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
isDesensitive: Joi.boolean().default(true), // 默认true就是需要脱敏
|
isDesensitive: Joi.boolean().default(true), // 默认true就是需要脱敏
|
||||||
page: Joi.number().default(1),
|
page: Joi.number().default(1),
|
||||||
pageSize: Joi.number().default(10),
|
pageSize: Joi.number().default(10),
|
||||||
}).validateAsync(queryInfo);
|
}).validate(queryInfo);
|
||||||
const { surveyId, isDesensitive, page, pageSize } = validationResult;
|
if (error) {
|
||||||
const username = req.user.username;
|
this.logger.error(error.message, { req });
|
||||||
await this.surveyMetaService.checkSurveyAccess({
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
surveyId,
|
}
|
||||||
username,
|
const { surveyId, isDesensitive, page, pageSize } = value;
|
||||||
});
|
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||||
const { total, listHead, listBody } =
|
const { total, listHead, listBody } =
|
||||||
|
@ -7,7 +7,10 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Request,
|
Request,
|
||||||
|
SetMetadata,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
import * as Joi from 'joi';
|
||||||
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
import { SurveyConfService } from '../services/surveyConf.service';
|
import { SurveyConfService } from '../services/surveyConf.service';
|
||||||
@ -16,14 +19,18 @@ 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 { CreateSurveyDto } from '../dto/createSurvey.dto';
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
|
||||||
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';
|
import { Logger } from 'src/logger';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
|
||||||
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
|
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/survey')
|
@Controller('/api/survey')
|
||||||
@ -46,66 +53,57 @@ export class SurveyController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Post('/createSurvey')
|
@Post('/createSurvey')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.createFrom')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
|
@UseGuards(WorkspaceGuard)
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.READ_SURVEY])
|
||||||
|
@SetMetadata('workspaceId', { key: 'body.workspaceId', optional: true })
|
||||||
|
@UseGuards(Authentication)
|
||||||
async createSurvey(
|
async createSurvey(
|
||||||
@Body()
|
@Body()
|
||||||
reqBody,
|
reqBody: CreateSurveyDto,
|
||||||
@Request()
|
@Request()
|
||||||
req,
|
req,
|
||||||
) {
|
) {
|
||||||
let validationResult;
|
const { error, value } = CreateSurveyDto.validate(reqBody);
|
||||||
try {
|
if (error) {
|
||||||
validationResult = await Joi.object({
|
|
||||||
title: Joi.string().required(),
|
|
||||||
remark: Joi.string().allow(null, '').default(''),
|
|
||||||
surveyType: Joi.string().when('createMethod', {
|
|
||||||
is: 'copy',
|
|
||||||
then: Joi.allow(null),
|
|
||||||
otherwise: Joi.required(),
|
|
||||||
}),
|
|
||||||
createMethod: Joi.string().allow(null).default('basic'),
|
|
||||||
createFrom: Joi.string().when('createMethod', {
|
|
||||||
is: 'copy',
|
|
||||||
then: Joi.required(),
|
|
||||||
otherwise: Joi.allow(null),
|
|
||||||
}),
|
|
||||||
}).validateAsync(reqBody);
|
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`createSurvey_parameter error: ${error.message}`, {
|
this.logger.error(`createSurvey_parameter error: ${error.message}`, {
|
||||||
req,
|
req,
|
||||||
});
|
});
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
|
||||||
const { title, remark, createMethod, createFrom } = validationResult;
|
const { title, remark, createMethod, createFrom } = value;
|
||||||
|
|
||||||
const username = req.user.username;
|
let surveyType = '',
|
||||||
let surveyType = '';
|
workspaceId = null;
|
||||||
if (createMethod === 'copy') {
|
if (createMethod === 'copy') {
|
||||||
const survey = await this.surveyMetaService.checkSurveyAccess({
|
const survey = req.surveyMeta;
|
||||||
surveyId: createFrom,
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
surveyType = survey.surveyType;
|
surveyType = survey.surveyType;
|
||||||
|
workspaceId = survey.workspaceId;
|
||||||
} else {
|
} else {
|
||||||
surveyType = validationResult.surveyType;
|
surveyType = value.surveyType;
|
||||||
|
workspaceId = value.workspaceId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const surveyMeta = await this.surveyMetaService.createSurveyMeta({
|
const surveyMeta = await this.surveyMetaService.createSurveyMeta({
|
||||||
title,
|
title,
|
||||||
remark,
|
remark,
|
||||||
surveyType,
|
surveyType,
|
||||||
username,
|
username: req.user.username,
|
||||||
|
userId: req.user._id.toString(),
|
||||||
createMethod,
|
createMethod,
|
||||||
createFrom,
|
createFrom,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
await this.surveyConfService.createSurveyConf({
|
await this.surveyConfService.createSurveyConf({
|
||||||
surveyId: surveyMeta._id.toString(),
|
surveyId: surveyMeta._id.toString(),
|
||||||
surveyType: surveyType,
|
surveyType: surveyType,
|
||||||
createMethod: validationResult.createMethod,
|
createMethod: value.createMethod,
|
||||||
createFrom: validationResult.createFrom,
|
createFrom: value.createFrom,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -115,26 +113,30 @@ export class SurveyController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Post('/updateConf')
|
@Post('/updateConf')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
async updateConf(
|
async updateConf(
|
||||||
@Body()
|
@Body()
|
||||||
surveyInfo,
|
surveyInfo,
|
||||||
@Request()
|
@Request()
|
||||||
req,
|
req,
|
||||||
) {
|
) {
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
configData: Joi.any().required(),
|
configData: Joi.any().required(),
|
||||||
}).validateAsync(surveyInfo);
|
}).validate(surveyInfo);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const surveyId = validationResult.surveyId;
|
const surveyId = value.surveyId;
|
||||||
await this.surveyMetaService.checkSurveyAccess({
|
|
||||||
surveyId,
|
const configData = value.configData;
|
||||||
username,
|
|
||||||
});
|
|
||||||
const configData = validationResult.configData;
|
|
||||||
await this.surveyConfService.saveSurveyConf({
|
await this.surveyConfService.saveSurveyConf({
|
||||||
surveyId,
|
surveyId,
|
||||||
schema: configData,
|
schema: configData,
|
||||||
@ -153,23 +155,18 @@ export class SurveyController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
@Post('/deleteSurvey')
|
@Post('/deleteSurvey')
|
||||||
async deleteSurvey(@Body() reqBody, @Request() req) {
|
@UseGuards(SurveyGuard)
|
||||||
const validationResult = await Joi.object({
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
surveyId: Joi.string().required(),
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
}).validateAsync(reqBody, { allowUnknown: true });
|
@UseGuards(Authentication)
|
||||||
const username = req.user.username;
|
async deleteSurvey(@Request() req) {
|
||||||
const surveyId = validationResult.surveyId;
|
const surveyMeta = req.surveyMeta;
|
||||||
const survey = await this.surveyMetaService.checkSurveyAccess({
|
|
||||||
surveyId,
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
|
|
||||||
await this.surveyMetaService.deleteSurveyMeta(survey);
|
await this.surveyMetaService.deleteSurveyMeta(surveyMeta);
|
||||||
await this.responseSchemaService.deleteResponseSchema({
|
await this.responseSchemaService.deleteResponseSchema({
|
||||||
surveyPath: survey.surveyPath,
|
surveyPath: surveyMeta.surveyPath,
|
||||||
});
|
});
|
||||||
|
|
||||||
return {
|
return {
|
||||||
@ -177,9 +174,16 @@ export class SurveyController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Get('/getSurvey')
|
@Get('/getSurvey')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'query.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
])
|
||||||
|
@UseGuards(Authentication)
|
||||||
async getSurvey(
|
async getSurvey(
|
||||||
@Query()
|
@Query()
|
||||||
queryInfo: {
|
queryInfo: {
|
||||||
@ -188,19 +192,28 @@ export class SurveyController {
|
|||||||
@Request()
|
@Request()
|
||||||
req,
|
req,
|
||||||
) {
|
) {
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
}).validateAsync(queryInfo);
|
}).validate(queryInfo);
|
||||||
|
|
||||||
const username = req.user.username;
|
if (error) {
|
||||||
const surveyId = validationResult.surveyId;
|
this.logger.error(error.message, { req });
|
||||||
const surveyMeta = await this.surveyMetaService.checkSurveyAccess({
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
surveyId,
|
}
|
||||||
username,
|
|
||||||
});
|
const surveyId = value.surveyId;
|
||||||
|
const surveyMeta = req.surveyMeta;
|
||||||
const surveyConf =
|
const surveyConf =
|
||||||
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
||||||
|
|
||||||
|
surveyMeta.currentUserId = req.user._id.toString();
|
||||||
|
if (req.collaborator) {
|
||||||
|
surveyMeta.isCollaborated = true;
|
||||||
|
surveyMeta.currentPermission = req.collaborator.permissions;
|
||||||
|
} else {
|
||||||
|
surveyMeta.isCollaborated = false;
|
||||||
|
}
|
||||||
|
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
data: {
|
data: {
|
||||||
@ -210,24 +223,28 @@ export class SurveyController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Post('/publishSurvey')
|
@Post('/publishSurvey')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
async publishSurvey(
|
async publishSurvey(
|
||||||
@Body()
|
@Body()
|
||||||
surveyInfo,
|
surveyInfo,
|
||||||
@Request()
|
@Request()
|
||||||
req,
|
req,
|
||||||
) {
|
) {
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
}).validateAsync(surveyInfo);
|
}).validate(surveyInfo);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(error.message, { req });
|
||||||
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const surveyId = validationResult.surveyId;
|
const surveyId = value.surveyId;
|
||||||
const surveyMeta = await this.surveyMetaService.checkSurveyAccess({
|
const surveyMeta = req.surveyMeta;
|
||||||
surveyId,
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
const surveyConf =
|
const surveyConf =
|
||||||
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
||||||
|
|
||||||
|
@ -4,48 +4,59 @@ import {
|
|||||||
Query,
|
Query,
|
||||||
HttpCode,
|
HttpCode,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
|
SetMetadata,
|
||||||
Request,
|
Request,
|
||||||
} from '@nestjs/common';
|
} from '@nestjs/common';
|
||||||
|
|
||||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
|
||||||
|
|
||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
import { Authtication } from 'src/guards/authtication';
|
|
||||||
|
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/surveyHisotry')
|
@Controller('/api/surveyHisotry')
|
||||||
export class SurveyHistoryController {
|
export class SurveyHistoryController {
|
||||||
constructor(
|
constructor(
|
||||||
private readonly surveyHistoryService: SurveyHistoryService,
|
private readonly surveyHistoryService: SurveyHistoryService,
|
||||||
private readonly surveyMetaService: SurveyMetaService,
|
private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Get('/getList')
|
@Get('/getList')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'query.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
])
|
||||||
|
@UseGuards(Authentication)
|
||||||
async getList(
|
async getList(
|
||||||
@Query()
|
@Query()
|
||||||
queryInfo: {
|
queryInfo: {
|
||||||
surveyId: string;
|
surveyId: string;
|
||||||
historyType: string;
|
historyType: string;
|
||||||
},
|
},
|
||||||
@Request()
|
@Request() req,
|
||||||
req,
|
|
||||||
) {
|
) {
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyId: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
historyType: Joi.string().required(),
|
historyType: Joi.string().required(),
|
||||||
}).validateAsync(queryInfo);
|
}).validate(queryInfo);
|
||||||
|
|
||||||
const username = req.user.username;
|
if (error) {
|
||||||
const surveyId = validationResult.surveyId;
|
this.logger.error(error.message, { req });
|
||||||
const historyType = validationResult.historyType;
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
await this.surveyMetaService.checkSurveyAccess({
|
}
|
||||||
surveyId,
|
|
||||||
username,
|
const surveyId = value.surveyId;
|
||||||
});
|
const historyType = value.historyType;
|
||||||
const data = await this.surveyHistoryService.getHistoryList({
|
const data = await this.surveyHistoryService.getHistoryList({
|
||||||
surveyId,
|
surveyId,
|
||||||
historyType,
|
historyType,
|
||||||
|
@ -7,18 +7,26 @@ import {
|
|||||||
HttpCode,
|
HttpCode,
|
||||||
UseGuards,
|
UseGuards,
|
||||||
Request,
|
Request,
|
||||||
|
SetMetadata,
|
||||||
} 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 { ApiTags } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||||
|
|
||||||
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 { Authentication } from 'src/guards/authentication.guard';
|
||||||
import { Logger } from 'src/logger';
|
import { Logger } from 'src/logger';
|
||||||
|
import { SurveyGuard } from 'src/guards/survey.guard';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
|
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||||
|
|
||||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
import { GetSurveyListDto } from '../dto/getSurveyMetaList.dto';
|
||||||
|
import { CollaboratorService } from '../services/collaborator.service';
|
||||||
|
|
||||||
@ApiTags('survey')
|
@ApiTags('survey')
|
||||||
@Controller('/api/survey')
|
@Controller('/api/survey')
|
||||||
@ -26,34 +34,31 @@ export class SurveyMetaController {
|
|||||||
constructor(
|
constructor(
|
||||||
private readonly surveyMetaService: SurveyMetaService,
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
private readonly logger: Logger,
|
private readonly logger: Logger,
|
||||||
|
private readonly collaboratorService: CollaboratorService,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
|
||||||
@Post('/updateMeta')
|
@Post('/updateMeta')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
|
@UseGuards(SurveyGuard)
|
||||||
|
@SetMetadata('surveyId', 'body.surveyId')
|
||||||
|
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||||
|
@UseGuards(Authentication)
|
||||||
async updateMeta(@Body() reqBody, @Request() req) {
|
async updateMeta(@Body() reqBody, @Request() req) {
|
||||||
let validationResult;
|
const { value, error } = Joi.object({
|
||||||
try {
|
title: Joi.string().required(),
|
||||||
validationResult = await Joi.object({
|
remark: Joi.string().allow(null, '').default(''),
|
||||||
title: Joi.string().required(),
|
surveyId: Joi.string().required(),
|
||||||
remark: Joi.string().allow(null, '').default(''),
|
}).validate(reqBody, { allowUnknown: true });
|
||||||
surveyId: Joi.string().required(),
|
|
||||||
}).validateAsync(reqBody, { allowUnknown: true });
|
if (error) {
|
||||||
} catch (error) {
|
|
||||||
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
||||||
req,
|
req,
|
||||||
});
|
});
|
||||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
}
|
}
|
||||||
|
const survey = req.surveyMeta;
|
||||||
const username = req.user.username;
|
survey.title = value.title;
|
||||||
const surveyId = validationResult.surveyId;
|
survey.remark = value.remark;
|
||||||
const survey = await this.surveyMetaService.checkSurveyAccess({
|
|
||||||
surveyId,
|
|
||||||
username,
|
|
||||||
});
|
|
||||||
survey.title = validationResult.title;
|
|
||||||
survey.remark = validationResult.remark;
|
|
||||||
|
|
||||||
await this.surveyMetaService.editSurveyMeta(survey);
|
await this.surveyMetaService.editSurveyMeta(survey);
|
||||||
|
|
||||||
@ -62,52 +67,58 @@ export class SurveyMetaController {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@UseGuards(Authtication)
|
@UseGuards(WorkspaceGuard)
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.READ_SURVEY])
|
||||||
|
@SetMetadata('workspaceId', { optional: true, key: 'query.workspaceId' })
|
||||||
|
@UseGuards(Authentication)
|
||||||
@Get('/getList')
|
@Get('/getList')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async getList(
|
async getList(
|
||||||
@Query()
|
@Query()
|
||||||
queryInfo: {
|
queryInfo: GetSurveyListDto,
|
||||||
curPage: number;
|
|
||||||
pageSize: number;
|
|
||||||
},
|
|
||||||
@Request()
|
@Request()
|
||||||
req,
|
req,
|
||||||
) {
|
) {
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = GetSurveyListDto.validate(queryInfo);
|
||||||
curPage: Joi.number().required(),
|
if (error) {
|
||||||
pageSize: Joi.number().allow(null).default(10),
|
this.logger.error(error.message, { req });
|
||||||
filter: Joi.string().allow(null),
|
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
order: Joi.string().allow(null),
|
}
|
||||||
}).validateAsync(queryInfo);
|
const { curPage, pageSize, workspaceId } = value;
|
||||||
const { curPage, pageSize } = validationResult;
|
|
||||||
let filter = {},
|
let filter = {},
|
||||||
order = {};
|
order = {};
|
||||||
if (validationResult.filter) {
|
if (value.filter) {
|
||||||
try {
|
try {
|
||||||
filter = getFilter(
|
filter = getFilter(JSON.parse(decodeURIComponent(value.filter)));
|
||||||
JSON.parse(decodeURIComponent(validationResult.filter)),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
this.logger.error(error.message, { req });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (validationResult.order) {
|
if (value.order) {
|
||||||
try {
|
try {
|
||||||
order = order = getOrder(
|
order = order = getOrder(JSON.parse(decodeURIComponent(value.order)));
|
||||||
JSON.parse(decodeURIComponent(validationResult.order)),
|
|
||||||
);
|
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error);
|
this.logger.error(error.message, { req });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
const userId = req.user._id.toString();
|
||||||
|
const cooperationList =
|
||||||
|
await this.collaboratorService.getCollaboratorListByUserId({ userId });
|
||||||
|
const cooperSurveyIdMap = cooperationList.reduce((pre, cur) => {
|
||||||
|
pre[cur.surveyId] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
const surveyIdList = cooperationList.map((item) => item.surveyId);
|
||||||
const username = req.user.username;
|
const username = req.user.username;
|
||||||
const data = await this.surveyMetaService.getSurveyMetaList({
|
const data = await this.surveyMetaService.getSurveyMetaList({
|
||||||
pageNum: curPage,
|
pageNum: curPage,
|
||||||
pageSize: pageSize,
|
pageSize: pageSize,
|
||||||
|
userId,
|
||||||
username,
|
username,
|
||||||
filter,
|
filter,
|
||||||
order,
|
order,
|
||||||
|
workspaceId,
|
||||||
|
surveyIdList,
|
||||||
});
|
});
|
||||||
return {
|
return {
|
||||||
code: 200,
|
code: 200,
|
||||||
@ -121,6 +132,15 @@ export class SurveyMetaController {
|
|||||||
item.createDate = moment(item.createDate).format(fmt);
|
item.createDate = moment(item.createDate).format(fmt);
|
||||||
item.updateDate = moment(item.updateDate).format(fmt);
|
item.updateDate = moment(item.updateDate).format(fmt);
|
||||||
item.curStatus.date = moment(item.curStatus.date).format(fmt);
|
item.curStatus.date = moment(item.curStatus.date).format(fmt);
|
||||||
|
const surveyId = item._id.toString();
|
||||||
|
if (cooperSurveyIdMap[surveyId]) {
|
||||||
|
item.isCollaborated = true;
|
||||||
|
item.currentPermissions = cooperSurveyIdMap[surveyId].permissions;
|
||||||
|
} else {
|
||||||
|
item.isCollaborated = false;
|
||||||
|
item.currentPermissions = [];
|
||||||
|
}
|
||||||
|
item.currentUserId = userId;
|
||||||
return item;
|
return item;
|
||||||
}),
|
}),
|
||||||
},
|
},
|
||||||
|
51
server/src/modules/survey/dto/batchSaveCollaborator.dto.ts
Normal file
51
server/src/modules/survey/dto/batchSaveCollaborator.dto.ts
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
|
||||||
|
export class CollaboratorDto {
|
||||||
|
@ApiProperty({ description: '用户id', required: false })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ApiProperty({
|
||||||
|
description: '权限',
|
||||||
|
required: true,
|
||||||
|
isArray: true,
|
||||||
|
enum: [
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
permissions: Array<number>;
|
||||||
|
}
|
||||||
|
|
||||||
|
export class BatchSaveCollaboratorDto {
|
||||||
|
@ApiProperty({ description: '问卷id', required: true })
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '协作人列表', required: true, isArray: true })
|
||||||
|
collaborators: Array<CollaboratorDto>;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
surveyId: Joi.string().required(),
|
||||||
|
collaborators: Joi.array()
|
||||||
|
.allow(null)
|
||||||
|
.items(
|
||||||
|
Joi.object({
|
||||||
|
_id: Joi.string().allow(null, ''),
|
||||||
|
userId: Joi.string().required(),
|
||||||
|
permissions: Joi.array()
|
||||||
|
.required()
|
||||||
|
.items(
|
||||||
|
Joi.string().valid(
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}).validate(data, { allowUnknown: true });
|
||||||
|
}
|
||||||
|
}
|
28
server/src/modules/survey/dto/changeUserPermission.dto.ts
Normal file
28
server/src/modules/survey/dto/changeUserPermission.dto.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
|
||||||
|
export class ChangeUserPermissionDto {
|
||||||
|
@ApiProperty({ description: '问卷id', required: true })
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户id', required: false })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '权限', required: true })
|
||||||
|
permissions: Array<string>;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
surveyId: Joi.string(),
|
||||||
|
userId: Joi.string(),
|
||||||
|
permissions: Joi.array().items(
|
||||||
|
Joi.string().valid(
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
28
server/src/modules/survey/dto/createCollaborator.dto.ts
Normal file
28
server/src/modules/survey/dto/createCollaborator.dto.ts
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||||
|
|
||||||
|
export class CreateCollaboratorDto {
|
||||||
|
@ApiProperty({ description: '问卷id', required: true })
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户id', required: false })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '权限', required: true, enum: SURVEY_PERMISSION })
|
||||||
|
permissions: Array<string>;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
surveyId: Joi.string(),
|
||||||
|
userId: Joi.string(),
|
||||||
|
permissions: Joi.array().items(
|
||||||
|
Joi.string().valid(
|
||||||
|
SURVEY_PERMISSION.SURVEY_CONF_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_COOPERATION_MANAGE,
|
||||||
|
SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE,
|
||||||
|
),
|
||||||
|
),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
41
server/src/modules/survey/dto/createSurvey.dto.ts
Normal file
41
server/src/modules/survey/dto/createSurvey.dto.ts
Normal file
@ -0,0 +1,41 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
|
||||||
|
export class CreateSurveyDto {
|
||||||
|
@ApiProperty({ description: '问卷标题', required: true })
|
||||||
|
title: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '问卷备注', required: false })
|
||||||
|
remark: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '问卷类型,复制问卷必传', required: false })
|
||||||
|
surveyType: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建方法', required: false })
|
||||||
|
createMethod: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '创建来源', required: false })
|
||||||
|
createFrom: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '问卷创建在哪个空间下', required: false })
|
||||||
|
workspaceId?: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
title: Joi.string().required(),
|
||||||
|
remark: Joi.string().allow(null, '').default(''),
|
||||||
|
surveyType: Joi.string().when('createMethod', {
|
||||||
|
is: 'copy',
|
||||||
|
then: Joi.allow(null),
|
||||||
|
otherwise: Joi.required(),
|
||||||
|
}),
|
||||||
|
createMethod: Joi.string().allow(null).valid('copy').default('basic'),
|
||||||
|
createFrom: Joi.string().when('createMethod', {
|
||||||
|
is: 'copy',
|
||||||
|
then: Joi.required(),
|
||||||
|
otherwise: Joi.allow(null),
|
||||||
|
}),
|
||||||
|
workspaceId: Joi.string().allow(null, ''),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
|
||||||
|
export class GetSurveyCollaboratorListDto {
|
||||||
|
@ApiProperty({ description: '问卷id', required: true })
|
||||||
|
surveyId: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
surveyId: Joi.string().required(),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
29
server/src/modules/survey/dto/getSurveyMetaList.dto.ts
Normal file
29
server/src/modules/survey/dto/getSurveyMetaList.dto.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
|
||||||
|
export class GetSurveyListDto {
|
||||||
|
@ApiProperty({ description: '当前页码', required: true })
|
||||||
|
curPage: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '分页', required: false })
|
||||||
|
pageSize: number;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '过滤调教', required: false })
|
||||||
|
filter?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '排序条件', required: false })
|
||||||
|
order?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '空间id', required: false })
|
||||||
|
workspaceId?: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
curPage: Joi.number().required(),
|
||||||
|
pageSize: Joi.number().allow(null).default(10),
|
||||||
|
filter: Joi.string().allow(null),
|
||||||
|
order: Joi.string().allow(null),
|
||||||
|
workspaceId: Joi.string().allow(null, ''),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
157
server/src/modules/survey/services/collaborator.service.ts
Normal file
157
server/src/modules/survey/services/collaborator.service.ts
Normal file
@ -0,0 +1,157 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { Collaborator } from 'src/models/collaborator.entity';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class CollaboratorService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Collaborator)
|
||||||
|
private readonly collaboratorRepository: MongoRepository<Collaborator>,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create({ surveyId, userId, permissions }) {
|
||||||
|
const collaborator = this.collaboratorRepository.create({
|
||||||
|
surveyId,
|
||||||
|
userId,
|
||||||
|
permissions,
|
||||||
|
});
|
||||||
|
return this.collaboratorRepository.save(collaborator);
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchCreate({ surveyId, collaboratorList }) {
|
||||||
|
const res = await this.collaboratorRepository.insertMany(
|
||||||
|
collaboratorList.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
surveyId,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
return res;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getSurveyCollaboratorList({ surveyId }) {
|
||||||
|
const list = await this.collaboratorRepository.find({
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCollaboratorListByIds({ idList }) {
|
||||||
|
const list = await this.collaboratorRepository.find({
|
||||||
|
_id: {
|
||||||
|
$in: idList.map((item) => new ObjectId(item)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return list;
|
||||||
|
}
|
||||||
|
|
||||||
|
async getCollaborator({ userId, surveyId }) {
|
||||||
|
const info = await this.collaboratorRepository.findOne({
|
||||||
|
where: {
|
||||||
|
surveyId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return info;
|
||||||
|
}
|
||||||
|
|
||||||
|
async changeUserPermission({ userId, surveyId, permission }) {
|
||||||
|
const updateRes = await this.collaboratorRepository.updateOne(
|
||||||
|
{
|
||||||
|
surveyId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
permission,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return updateRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteCollaborator({ userId, surveyId }) {
|
||||||
|
const delRes = await this.collaboratorRepository.deleteOne({
|
||||||
|
userId,
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
return delRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchDelete({
|
||||||
|
idList,
|
||||||
|
neIdList,
|
||||||
|
userIdList,
|
||||||
|
surveyId,
|
||||||
|
}: {
|
||||||
|
idList?: Array<string>;
|
||||||
|
neIdList?: Array<string>;
|
||||||
|
userIdList?: Array<string>;
|
||||||
|
surveyId: string;
|
||||||
|
}) {
|
||||||
|
const query: Record<string, any> = {
|
||||||
|
surveyId,
|
||||||
|
$or: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
if (Array.isArray(userIdList) && userIdList.length > 0) {
|
||||||
|
query.$or.push({
|
||||||
|
userId: {
|
||||||
|
$in: userIdList,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (
|
||||||
|
(Array.isArray(idList) && idList.length > 0) ||
|
||||||
|
(Array.isArray(neIdList) && neIdList.length > 0)
|
||||||
|
) {
|
||||||
|
const idQuery: Record<string, any> = {
|
||||||
|
_id: {},
|
||||||
|
};
|
||||||
|
if (idList && idList.length > 0) {
|
||||||
|
idQuery._id.$in = idList.map((item) => new ObjectId(item));
|
||||||
|
}
|
||||||
|
if (neIdList && neIdList.length > 0) {
|
||||||
|
idQuery._id.$nin = neIdList.map((item) => new ObjectId(item));
|
||||||
|
}
|
||||||
|
query.$or.push(idQuery);
|
||||||
|
}
|
||||||
|
this.logger.info(JSON.stringify(query));
|
||||||
|
const delRes = await this.collaboratorRepository.deleteMany(query);
|
||||||
|
return delRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchDeleteBySurveyId(surveyId) {
|
||||||
|
const delRes = await this.collaboratorRepository.deleteMany({
|
||||||
|
surveyId,
|
||||||
|
});
|
||||||
|
return delRes;
|
||||||
|
}
|
||||||
|
|
||||||
|
updateById({ collaboratorId, permissions }) {
|
||||||
|
return this.collaboratorRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(collaboratorId),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
permissions,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
getCollaboratorListByUserId({ userId }) {
|
||||||
|
return this.collaboratorRepository.find({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
@ -4,9 +4,7 @@ import { MongoRepository, FindOptionsOrder } from 'typeorm';
|
|||||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { ObjectId } from 'mongodb';
|
import { ObjectId } from 'mongodb';
|
||||||
import { NoSurveyPermissionException } from 'src/exceptions/noSurveyPermissionException';
|
|
||||||
import { HttpException } from 'src/exceptions/httpException';
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
|
||||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||||
|
|
||||||
@ -34,18 +32,10 @@ export class SurveyMetaService {
|
|||||||
return surveyPath;
|
return surveyPath;
|
||||||
}
|
}
|
||||||
|
|
||||||
async checkSurveyAccess({ surveyId, username }) {
|
async getSurveyById({ surveyId }) {
|
||||||
const survey = await this.surveyRepository.findOne({
|
return this.surveyRepository.findOne({
|
||||||
where: { _id: new ObjectId(surveyId) },
|
where: { _id: new ObjectId(surveyId) },
|
||||||
});
|
});
|
||||||
|
|
||||||
if (!survey) {
|
|
||||||
throw new SurveyNotFoundException('问卷不存在');
|
|
||||||
}
|
|
||||||
if (survey.owner !== username) {
|
|
||||||
throw new NoSurveyPermissionException('没有权限');
|
|
||||||
}
|
|
||||||
return survey;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
async createSurveyMeta(params: {
|
async createSurveyMeta(params: {
|
||||||
@ -53,11 +43,21 @@ export class SurveyMetaService {
|
|||||||
remark: string;
|
remark: string;
|
||||||
surveyType: string;
|
surveyType: string;
|
||||||
username: string;
|
username: string;
|
||||||
|
userId: string;
|
||||||
createMethod: string;
|
createMethod: string;
|
||||||
createFrom: string;
|
createFrom: string;
|
||||||
|
workspaceId?: string;
|
||||||
}) {
|
}) {
|
||||||
const { title, remark, surveyType, username, createMethod, createFrom } =
|
const {
|
||||||
params;
|
title,
|
||||||
|
remark,
|
||||||
|
surveyType,
|
||||||
|
username,
|
||||||
|
createMethod,
|
||||||
|
createFrom,
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
} = params;
|
||||||
const surveyPath = await this.getNewSurveyPath();
|
const surveyPath = await this.getNewSurveyPath();
|
||||||
const newSurvey = this.surveyRepository.create({
|
const newSurvey = this.surveyRepository.create({
|
||||||
title,
|
title,
|
||||||
@ -66,8 +66,10 @@ export class SurveyMetaService {
|
|||||||
surveyPath,
|
surveyPath,
|
||||||
creator: username,
|
creator: username,
|
||||||
owner: username,
|
owner: username,
|
||||||
|
ownerId: userId,
|
||||||
createMethod,
|
createMethod,
|
||||||
createFrom,
|
createFrom,
|
||||||
|
workspaceId,
|
||||||
});
|
});
|
||||||
|
|
||||||
return await this.surveyRepository.save(newSurvey);
|
return await this.surveyRepository.save(newSurvey);
|
||||||
@ -112,22 +114,48 @@ export class SurveyMetaService {
|
|||||||
pageNum: number;
|
pageNum: number;
|
||||||
pageSize: number;
|
pageSize: number;
|
||||||
username: string;
|
username: string;
|
||||||
|
userId: string;
|
||||||
filter: Record<string, any>;
|
filter: Record<string, any>;
|
||||||
order: Record<string, any>;
|
order: Record<string, any>;
|
||||||
|
workspaceId?: string;
|
||||||
|
surveyIdList?: Array<string>;
|
||||||
}): Promise<{ data: any[]; count: number }> {
|
}): Promise<{ data: any[]; count: number }> {
|
||||||
const { pageNum, pageSize, username } = condition;
|
const { pageNum, pageSize, userId, username, workspaceId, surveyIdList } =
|
||||||
|
condition;
|
||||||
const skip = (pageNum - 1) * pageSize;
|
const skip = (pageNum - 1) * pageSize;
|
||||||
try {
|
try {
|
||||||
const query = Object.assign(
|
const query: Record<string, any> = Object.assign(
|
||||||
{},
|
{},
|
||||||
{
|
{
|
||||||
owner: username,
|
|
||||||
'curStatus.status': {
|
'curStatus.status': {
|
||||||
$ne: 'removed',
|
$ne: 'removed',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
condition.filter,
|
condition.filter,
|
||||||
);
|
);
|
||||||
|
if (workspaceId) {
|
||||||
|
query.workspaceId = workspaceId;
|
||||||
|
} else {
|
||||||
|
query.workspaceId = {
|
||||||
|
$exists: false,
|
||||||
|
};
|
||||||
|
// 引入空间之前,新建的问卷只有owner字段,引入空间之后,新建的问卷多了ownerId字段,使用owenrId字段进行关联更加合理,此处做了兼容
|
||||||
|
query.$or = [
|
||||||
|
{
|
||||||
|
owner: username,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
ownerId: userId,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
if (Array.isArray(surveyIdList) && surveyIdList.length > 0) {
|
||||||
|
query.$or.push({
|
||||||
|
_id: {
|
||||||
|
$in: surveyIdList.map((item) => new ObjectId(item)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
const order =
|
const order =
|
||||||
condition.order && Object.keys(condition.order).length > 0
|
condition.order && Object.keys(condition.order).length > 0
|
||||||
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
||||||
@ -160,4 +188,14 @@ export class SurveyMetaService {
|
|||||||
}
|
}
|
||||||
return this.surveyRepository.save(surveyMeta);
|
return this.surveyRepository.save(surveyMeta);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
async countSurveyMetaByWorkspaceId({ workspaceId }) {
|
||||||
|
const total = await this.surveyRepository.count({
|
||||||
|
workspaceId,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
return total;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -6,18 +6,21 @@ import { LoggerProvider } from 'src/logger/logger.provider';
|
|||||||
|
|
||||||
import { SurveyResponseModule } from '../surveyResponse/surveyResponse.module';
|
import { SurveyResponseModule } from '../surveyResponse/surveyResponse.module';
|
||||||
import { AuthModule } from '../auth/auth.module';
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||||
|
|
||||||
import { DataStatisticController } from './controllers/dataStatistic.controller';
|
import { DataStatisticController } from './controllers/dataStatistic.controller';
|
||||||
import { SurveyController } from './controllers/survey.controller';
|
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 { CollaboratorController } from './controllers/collaborator.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 { Collaborator } from 'src/models/collaborator.entity';
|
||||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
|
|
||||||
import { DataStatisticService } from './services/dataStatistic.service';
|
import { DataStatisticService } from './services/dataStatistic.service';
|
||||||
@ -25,6 +28,7 @@ import { SurveyConfService } from './services/surveyConf.service';
|
|||||||
import { SurveyHistoryService } from './services/surveyHistory.service';
|
import { SurveyHistoryService } from './services/surveyHistory.service';
|
||||||
import { SurveyMetaService } from './services/surveyMeta.service';
|
import { SurveyMetaService } from './services/surveyMeta.service';
|
||||||
import { ContentSecurityService } from './services/contentSecurity.service';
|
import { ContentSecurityService } from './services/contentSecurity.service';
|
||||||
|
import { CollaboratorService } from './services/collaborator.service';
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
@ -34,10 +38,12 @@ import { ContentSecurityService } from './services/contentSecurity.service';
|
|||||||
SurveyHistory,
|
SurveyHistory,
|
||||||
SurveyResponse,
|
SurveyResponse,
|
||||||
Word,
|
Word,
|
||||||
|
Collaborator,
|
||||||
]),
|
]),
|
||||||
ConfigModule,
|
ConfigModule,
|
||||||
SurveyResponseModule,
|
SurveyResponseModule,
|
||||||
AuthModule,
|
AuthModule,
|
||||||
|
WorkspaceModule,
|
||||||
],
|
],
|
||||||
controllers: [
|
controllers: [
|
||||||
DataStatisticController,
|
DataStatisticController,
|
||||||
@ -45,6 +51,7 @@ import { ContentSecurityService } from './services/contentSecurity.service';
|
|||||||
SurveyHistoryController,
|
SurveyHistoryController,
|
||||||
SurveyMetaController,
|
SurveyMetaController,
|
||||||
SurveyUIController,
|
SurveyUIController,
|
||||||
|
CollaboratorController,
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
DataStatisticService,
|
DataStatisticService,
|
||||||
@ -53,6 +60,7 @@ import { ContentSecurityService } from './services/contentSecurity.service';
|
|||||||
SurveyMetaService,
|
SurveyMetaService,
|
||||||
PluginManagerProvider,
|
PluginManagerProvider,
|
||||||
ContentSecurityService,
|
ContentSecurityService,
|
||||||
|
CollaboratorService,
|
||||||
LoggerProvider,
|
LoggerProvider,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
|
15
server/src/modules/survey/utils/splitCollaborator.ts
Normal file
15
server/src/modules/survey/utils/splitCollaborator.ts
Normal file
@ -0,0 +1,15 @@
|
|||||||
|
export const splitCollaborators = (collaboratorList) => {
|
||||||
|
const newCollaborator = [],
|
||||||
|
existsCollaborator = [];
|
||||||
|
for (const collaborator of collaboratorList) {
|
||||||
|
if (collaborator._id) {
|
||||||
|
existsCollaborator.push(collaborator);
|
||||||
|
} else {
|
||||||
|
newCollaborator.push(collaborator);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
newCollaborator,
|
||||||
|
existsCollaborator,
|
||||||
|
};
|
||||||
|
};
|
@ -20,6 +20,7 @@ import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugi
|
|||||||
|
|
||||||
import { RECORD_STATUS } from 'src/enums';
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
const mockDecryptErrorBody = {
|
const mockDecryptErrorBody = {
|
||||||
surveyPath: 'EBzdmnSp',
|
surveyPath: 'EBzdmnSp',
|
||||||
@ -116,6 +117,13 @@ describe('SurveyResponseController', () => {
|
|||||||
runResponseDataPush: jest.fn(),
|
runResponseDataPush: jest.fn(),
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
error: jest.fn(),
|
||||||
|
info: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
}).compile();
|
}).compile();
|
||||||
|
|
||||||
@ -197,7 +205,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
||||||
.mockResolvedValueOnce(undefined);
|
.mockResolvedValueOnce(undefined);
|
||||||
|
|
||||||
const result = await controller.createResponse(reqBody);
|
const result = await controller.createResponse(reqBody, {});
|
||||||
|
|
||||||
expect(result).toEqual({ code: 200, msg: '提交成功' });
|
expect(result).toEqual({ code: 200, msg: '提交成功' });
|
||||||
expect(
|
expect(
|
||||||
@ -244,7 +252,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValueOnce(null);
|
.mockResolvedValueOnce(null);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
SurveyNotFoundException,
|
SurveyNotFoundException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -253,7 +261,7 @@ describe('SurveyResponseController', () => {
|
|||||||
const reqBody = cloneDeep(mockSubmitData);
|
const reqBody = cloneDeep(mockSubmitData);
|
||||||
delete reqBody.sign;
|
delete reqBody.sign;
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -266,7 +274,7 @@ describe('SurveyResponseController', () => {
|
|||||||
const reqBody = cloneDeep(mockDecryptErrorBody);
|
const reqBody = cloneDeep(mockDecryptErrorBody);
|
||||||
reqBody.sign = 'mock sign';
|
reqBody.sign = 'mock sign';
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -282,7 +290,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValueOnce(mockResponseSchema);
|
.mockResolvedValueOnce(mockResponseSchema);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
@ -294,7 +302,7 @@ describe('SurveyResponseController', () => {
|
|||||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||||
.mockResolvedValueOnce(mockResponseSchema);
|
.mockResolvedValueOnce(mockResponseSchema);
|
||||||
|
|
||||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||||
HttpException,
|
HttpException,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
@ -1,4 +1,4 @@
|
|||||||
import { Controller, Post, Body, HttpCode } from '@nestjs/common';
|
import { Controller, Post, Body, HttpCode, Request } 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';
|
||||||
@ -16,6 +16,7 @@ import moment from 'moment';
|
|||||||
import * as Joi from 'joi';
|
import * as Joi from 'joi';
|
||||||
import * as forge from 'node-forge';
|
import * as forge from 'node-forge';
|
||||||
import { ApiTags } from '@nestjs/swagger';
|
import { ApiTags } from '@nestjs/swagger';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
@ApiTags('surveyResponse')
|
@ApiTags('surveyResponse')
|
||||||
@Controller('/api/surveyResponse')
|
@Controller('/api/surveyResponse')
|
||||||
@ -26,25 +27,33 @@ export class SurveyResponseController {
|
|||||||
private readonly surveyResponseService: SurveyResponseService,
|
private readonly surveyResponseService: SurveyResponseService,
|
||||||
private readonly clientEncryptService: ClientEncryptService,
|
private readonly clientEncryptService: ClientEncryptService,
|
||||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||||
|
private readonly logger: Logger,
|
||||||
) {}
|
) {}
|
||||||
|
|
||||||
@Post('/createResponse')
|
@Post('/createResponse')
|
||||||
@HttpCode(200)
|
@HttpCode(200)
|
||||||
async createResponse(@Body() reqBody) {
|
async createResponse(@Body() reqBody, @Request() req) {
|
||||||
// 检查签名
|
// 检查签名
|
||||||
checkSign(reqBody);
|
checkSign(reqBody);
|
||||||
// 校验参数
|
// 校验参数
|
||||||
const validationResult = await Joi.object({
|
const { value, error } = Joi.object({
|
||||||
surveyPath: Joi.string().required(),
|
surveyPath: Joi.string().required(),
|
||||||
data: Joi.any().required(),
|
data: Joi.any().required(),
|
||||||
encryptType: Joi.string(),
|
encryptType: Joi.string(),
|
||||||
sessionId: Joi.string(),
|
sessionId: Joi.string(),
|
||||||
clientTime: Joi.number().required(),
|
clientTime: Joi.number().required(),
|
||||||
difTime: Joi.number(),
|
difTime: Joi.number(),
|
||||||
}).validateAsync(reqBody, { allowUnknown: true });
|
}).validate(reqBody, { allowUnknown: true });
|
||||||
|
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
||||||
|
req,
|
||||||
|
});
|
||||||
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
|
||||||
const { surveyPath, encryptType, data, sessionId, clientTime, difTime } =
|
const { surveyPath, encryptType, data, sessionId, clientTime, difTime } =
|
||||||
validationResult;
|
value;
|
||||||
|
|
||||||
// 查询schema
|
// 查询schema
|
||||||
const responseSchema =
|
const responseSchema =
|
||||||
@ -153,8 +162,8 @@ export class SurveyResponseController {
|
|||||||
|
|
||||||
// 对用户提交的数据进行遍历处理
|
// 对用户提交的数据进行遍历处理
|
||||||
for (const field in decryptedData) {
|
for (const field in decryptedData) {
|
||||||
const value = decryptedData[field];
|
const val = decryptedData[field];
|
||||||
const values = Array.isArray(value) ? value : [value];
|
const vals = Array.isArray(val) ? val : [val];
|
||||||
if (field in optionTextAndId) {
|
if (field in optionTextAndId) {
|
||||||
// 记录选项的提交数量,用于投票题回显、或者拓展上限限制功能
|
// 记录选项的提交数量,用于投票题回显、或者拓展上限限制功能
|
||||||
const optionCountData: Record<string, any> =
|
const optionCountData: Record<string, any> =
|
||||||
@ -164,7 +173,7 @@ export class SurveyResponseController {
|
|||||||
type: 'option',
|
type: 'option',
|
||||||
})) || { total: 0 };
|
})) || { total: 0 };
|
||||||
optionCountData.total++;
|
optionCountData.total++;
|
||||||
for (const val of values) {
|
for (const val of vals) {
|
||||||
if (!optionCountData[val]) {
|
if (!optionCountData[val]) {
|
||||||
optionCountData[val] = 1;
|
optionCountData[val] = 1;
|
||||||
} else {
|
} else {
|
||||||
@ -183,7 +192,7 @@ export class SurveyResponseController {
|
|||||||
// 入库
|
// 入库
|
||||||
const surveyResponse =
|
const surveyResponse =
|
||||||
await this.surveyResponseService.createSurveyResponse({
|
await this.surveyResponseService.createSurveyResponse({
|
||||||
surveyPath: validationResult.surveyPath,
|
surveyPath: value.surveyPath,
|
||||||
data: decryptedData,
|
data: decryptedData,
|
||||||
clientTime,
|
clientTime,
|
||||||
difTime,
|
difTime,
|
||||||
|
@ -1,4 +1,6 @@
|
|||||||
import { Module } from '@nestjs/common';
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
import { MessageModule } from '../message/message.module';
|
import { MessageModule } from '../message/message.module';
|
||||||
|
|
||||||
@ -11,6 +13,7 @@ import { ResponseSchema } from 'src/models/responseSchema.entity';
|
|||||||
import { Counter } from 'src/models/counter.entity';
|
import { Counter } from 'src/models/counter.entity';
|
||||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
||||||
import { CounterController } from './controllers/counter.controller';
|
import { CounterController } from './controllers/counter.controller';
|
||||||
@ -18,9 +21,6 @@ import { ResponseSchemaController } from './controllers/responseSchema.controlle
|
|||||||
import { SurveyResponseController } from './controllers/surveyResponse.controller';
|
import { SurveyResponseController } from './controllers/surveyResponse.controller';
|
||||||
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';
|
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';
|
||||||
|
|
||||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
|
||||||
import { ConfigModule } from '@nestjs/config';
|
|
||||||
|
|
||||||
@Module({
|
@Module({
|
||||||
imports: [
|
imports: [
|
||||||
TypeOrmModule.forFeature([
|
TypeOrmModule.forFeature([
|
||||||
@ -44,6 +44,7 @@ import { ConfigModule } from '@nestjs/config';
|
|||||||
SurveyResponseService,
|
SurveyResponseService,
|
||||||
CounterService,
|
CounterService,
|
||||||
ClientEncryptService,
|
ClientEncryptService,
|
||||||
|
Logger,
|
||||||
],
|
],
|
||||||
exports: [
|
exports: [
|
||||||
ResponseSchemaService,
|
ResponseSchemaService,
|
||||||
|
50
server/src/modules/workspace/_test/splitMember.spec.ts
Normal file
50
server/src/modules/workspace/_test/splitMember.spec.ts
Normal file
@ -0,0 +1,50 @@
|
|||||||
|
import { splitMembers, Member } from '../utils/splitMember';
|
||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
describe('splitMembers', () => {
|
||||||
|
it('should split members into newMembers, adminMembers, and userMembers', () => {
|
||||||
|
const members = [
|
||||||
|
{ userId: 'user1', role: WORKSPACE_ROLE.ADMIN, _id: '1' },
|
||||||
|
{ userId: 'user2', role: WORKSPACE_ROLE.USER, _id: '2' },
|
||||||
|
{ userId: 'user3', role: WORKSPACE_ROLE.ADMIN, _id: '3' },
|
||||||
|
{ userId: 'user4', role: WORKSPACE_ROLE.USER, _id: '4' },
|
||||||
|
{ userId: 'user5', role: WORKSPACE_ROLE.USER },
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = splitMembers(members);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
newMembers: [{ userId: 'user5', role: WORKSPACE_ROLE.USER }],
|
||||||
|
adminMembers: ['1', '3'],
|
||||||
|
userMembers: ['2', '4'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle an empty members array', () => {
|
||||||
|
const members: Array<Member> = [];
|
||||||
|
|
||||||
|
const result = splitMembers(members);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
newMembers: [],
|
||||||
|
adminMembers: [],
|
||||||
|
userMembers: [],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle members with no role', () => {
|
||||||
|
const members = [
|
||||||
|
{ userId: 'user1', role: WORKSPACE_ROLE.ADMIN, _id: '1' },
|
||||||
|
{ userId: 'user2', role: '', _id: '2' },
|
||||||
|
{ userId: 'user3', role: '', _id: '3' },
|
||||||
|
];
|
||||||
|
|
||||||
|
const result = splitMembers(members);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
newMembers: [],
|
||||||
|
adminMembers: ['1'],
|
||||||
|
userMembers: ['2', '3'],
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
229
server/src/modules/workspace/_test/workspace.controller.spec.ts
Normal file
229
server/src/modules/workspace/_test/workspace.controller.spec.ts
Normal file
@ -0,0 +1,229 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { WorkspaceController } from '../controllers/workspace.controller';
|
||||||
|
import { WorkspaceService } from '../services/workspace.service';
|
||||||
|
import { WorkspaceMemberService } from '../services/workspaceMember.service';
|
||||||
|
import { CreateWorkspaceDto } from '../dto/createWorkspace.dto';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
import { Workspace } from 'src/models/workspace.entity';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
import { User } from 'src/models/user.entity';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
|
describe('WorkspaceController', () => {
|
||||||
|
let controller: WorkspaceController;
|
||||||
|
let workspaceService: WorkspaceService;
|
||||||
|
let workspaceMemberService: WorkspaceMemberService;
|
||||||
|
let userService: UserService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [WorkspaceController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: WorkspaceService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
findAllById: jest.fn(),
|
||||||
|
update: jest.fn(),
|
||||||
|
delete: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
batchCreate: jest.fn(),
|
||||||
|
findAllByUserId: jest.fn(),
|
||||||
|
batchUpdate: jest.fn(),
|
||||||
|
batchDelete: jest.fn(),
|
||||||
|
countByWorkspaceId: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: UserService,
|
||||||
|
useValue: {
|
||||||
|
getUserListByIds: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: SurveyMetaService,
|
||||||
|
useValue: {
|
||||||
|
countSurveyMetaByWorkspaceId: jest.fn().mockImplementation(() => {
|
||||||
|
return Math.floor(Math.random() * 10);
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: Logger,
|
||||||
|
useValue: {
|
||||||
|
info: jest.fn(),
|
||||||
|
error: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<WorkspaceController>(WorkspaceController);
|
||||||
|
workspaceService = module.get<WorkspaceService>(WorkspaceService);
|
||||||
|
workspaceMemberService = module.get<WorkspaceMemberService>(
|
||||||
|
WorkspaceMemberService,
|
||||||
|
);
|
||||||
|
userService = module.get<UserService>(UserService);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a workspace and return workspaceId', async () => {
|
||||||
|
const createWorkspaceDto: CreateWorkspaceDto = {
|
||||||
|
name: 'Test Workspace',
|
||||||
|
description: 'Test Description',
|
||||||
|
members: [{ userId: 'userId1', role: WORKSPACE_ROLE.USER }],
|
||||||
|
};
|
||||||
|
const req = { user: { _id: new ObjectId() } };
|
||||||
|
const createdWorkspace = { _id: new ObjectId() };
|
||||||
|
|
||||||
|
jest.spyOn(userService, 'getUserListByIds').mockResolvedValue([
|
||||||
|
{
|
||||||
|
_id: 'userId1',
|
||||||
|
},
|
||||||
|
] as unknown as Array<User>);
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceService, 'create')
|
||||||
|
.mockResolvedValue(createdWorkspace as Workspace);
|
||||||
|
jest.spyOn(workspaceMemberService, 'create').mockResolvedValue(null);
|
||||||
|
jest.spyOn(workspaceMemberService, 'batchCreate').mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await controller.create(createWorkspaceDto, req);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: { workspaceId: createdWorkspace._id.toString() },
|
||||||
|
});
|
||||||
|
expect(workspaceService.create).toHaveBeenCalledWith({
|
||||||
|
name: createWorkspaceDto.name,
|
||||||
|
description: createWorkspaceDto.description,
|
||||||
|
ownerId: req.user._id.toString(),
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.create).toHaveBeenCalledWith({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
workspaceId: createdWorkspace._id.toString(),
|
||||||
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.batchCreate).toHaveBeenCalledWith({
|
||||||
|
workspaceId: createdWorkspace._id.toString(),
|
||||||
|
members: createWorkspaceDto.members,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const createWorkspaceDto: CreateWorkspaceDto = {
|
||||||
|
name: '',
|
||||||
|
members: [],
|
||||||
|
};
|
||||||
|
const req = { user: { _id: new ObjectId() } };
|
||||||
|
|
||||||
|
await expect(controller.create(createWorkspaceDto, req)).rejects.toThrow(
|
||||||
|
HttpException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAll', () => {
|
||||||
|
it('should return a list of workspaces for the user', async () => {
|
||||||
|
const req = { user: { _id: new ObjectId() } };
|
||||||
|
const memberList = [{ workspaceId: new ObjectId().toString() }];
|
||||||
|
const workspaces = [{ _id: new ObjectId(), name: 'Test Workspace' }];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'findAllByUserId')
|
||||||
|
.mockResolvedValue(memberList as unknown as Array<WorkspaceMember>);
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceService, 'findAllById')
|
||||||
|
.mockResolvedValue(workspaces as Array<Workspace>);
|
||||||
|
|
||||||
|
jest.spyOn(userService, 'getUserListByIds').mockResolvedValue([]);
|
||||||
|
|
||||||
|
const result = await controller.findAll(req);
|
||||||
|
|
||||||
|
expect(result.code).toEqual(200);
|
||||||
|
expect(workspaceMemberService.findAllByUserId).toHaveBeenCalledWith({
|
||||||
|
userId: req.user._id.toString(),
|
||||||
|
});
|
||||||
|
expect(workspaceService.findAllById).toHaveBeenCalledWith({
|
||||||
|
workspaceIdList: memberList.map((item) => item.workspaceId),
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update a workspace and its members', async () => {
|
||||||
|
const id = new ObjectId().toString();
|
||||||
|
const userId = new ObjectId();
|
||||||
|
const members = {
|
||||||
|
newMembers: [{ userId: userId.toString(), role: WORKSPACE_ROLE.ADMIN }],
|
||||||
|
adminMembers: [],
|
||||||
|
userMembers: [],
|
||||||
|
};
|
||||||
|
jest.spyOn(userService, 'getUserListByIds').mockResolvedValue([
|
||||||
|
{
|
||||||
|
_id: userId,
|
||||||
|
},
|
||||||
|
] as Array<User>);
|
||||||
|
const updateDto = {
|
||||||
|
name: 'Updated Workspace',
|
||||||
|
members: [
|
||||||
|
...members.newMembers,
|
||||||
|
...members.adminMembers,
|
||||||
|
...members.userMembers,
|
||||||
|
],
|
||||||
|
};
|
||||||
|
const updateResult = { affected: 1, raw: '', generatedMaps: [] };
|
||||||
|
|
||||||
|
jest.spyOn(workspaceService, 'update').mockResolvedValue(updateResult);
|
||||||
|
jest.spyOn(workspaceMemberService, 'batchCreate').mockResolvedValue(null);
|
||||||
|
jest.spyOn(workspaceMemberService, 'batchUpdate').mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await controller.update(id, updateDto);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
});
|
||||||
|
expect(workspaceService.update).toHaveBeenCalledWith(id, {
|
||||||
|
name: updateDto.name,
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.batchCreate).toHaveBeenCalledWith({
|
||||||
|
workspaceId: id,
|
||||||
|
members: members.newMembers,
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.batchUpdate).toHaveBeenCalledWith({
|
||||||
|
idList: members.adminMembers,
|
||||||
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.batchUpdate).toHaveBeenCalledWith({
|
||||||
|
idList: members.userMembers,
|
||||||
|
role: WORKSPACE_ROLE.USER,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should delete a workspace', async () => {
|
||||||
|
const id = 'workspaceId';
|
||||||
|
|
||||||
|
jest.spyOn(workspaceService, 'delete').mockResolvedValue(null);
|
||||||
|
|
||||||
|
const result = await controller.delete(id);
|
||||||
|
|
||||||
|
expect(result).toEqual({ code: 200 });
|
||||||
|
expect(workspaceService.delete).toHaveBeenCalledWith(id);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
126
server/src/modules/workspace/_test/workspace.service.spec.ts
Normal file
126
server/src/modules/workspace/_test/workspace.service.spec.ts
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
import { WorkspaceService } from '../services/workspace.service';
|
||||||
|
import { Workspace } from 'src/models/workspace.entity';
|
||||||
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
|
describe('WorkspaceService', () => {
|
||||||
|
let service: WorkspaceService;
|
||||||
|
let workspaceRepository: MongoRepository<Workspace>;
|
||||||
|
let surveyMetaRepository: MongoRepository<SurveyMeta>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
WorkspaceService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(Workspace),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(SurveyMeta),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<WorkspaceService>(WorkspaceService);
|
||||||
|
workspaceRepository = module.get<MongoRepository<Workspace>>(
|
||||||
|
getRepositoryToken(Workspace),
|
||||||
|
);
|
||||||
|
surveyMetaRepository = module.get<MongoRepository<SurveyMeta>>(
|
||||||
|
getRepositoryToken(SurveyMeta),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a new workspace', async () => {
|
||||||
|
const workspace = {
|
||||||
|
name: 'Test Workspace',
|
||||||
|
description: 'Description',
|
||||||
|
ownerId: 'ownerId',
|
||||||
|
};
|
||||||
|
const createdWorkspace = { ...workspace, _id: new ObjectId() };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceRepository, 'create')
|
||||||
|
.mockReturnValue(createdWorkspace as any);
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceRepository, 'save')
|
||||||
|
.mockResolvedValue(createdWorkspace as any);
|
||||||
|
|
||||||
|
const result = await service.create(workspace);
|
||||||
|
|
||||||
|
expect(result).toEqual(createdWorkspace);
|
||||||
|
expect(workspaceRepository.create).toHaveBeenCalledWith(workspace);
|
||||||
|
expect(workspaceRepository.save).toHaveBeenCalledWith(createdWorkspace);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAllById', () => {
|
||||||
|
it('should return a list of workspaces', async () => {
|
||||||
|
const workspaceIdList = [
|
||||||
|
new ObjectId().toString(),
|
||||||
|
new ObjectId().toString(),
|
||||||
|
];
|
||||||
|
const workspaces = [
|
||||||
|
{ _id: workspaceIdList[0], name: 'Workspace 1' },
|
||||||
|
{ _id: workspaceIdList[1], name: 'Workspace 2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceRepository, 'find')
|
||||||
|
.mockResolvedValue(workspaces as any);
|
||||||
|
|
||||||
|
const result = await service.findAllById({ workspaceIdList });
|
||||||
|
|
||||||
|
expect(result).toEqual(workspaces);
|
||||||
|
expect(workspaceRepository.find).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('update', () => {
|
||||||
|
it('should update a workspace', async () => {
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
|
const updateData = { name: 'Updated Workspace' };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceRepository, 'update')
|
||||||
|
.mockResolvedValue({ affected: 1 } as any);
|
||||||
|
|
||||||
|
const result = await service.update(workspaceId, updateData);
|
||||||
|
|
||||||
|
expect(result).toEqual({ affected: 1 });
|
||||||
|
expect(workspaceRepository.update).toHaveBeenCalledWith(
|
||||||
|
workspaceId,
|
||||||
|
updateData,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should delete a workspace and update related surveyMeta', async () => {
|
||||||
|
const workspaceId = new ObjectId().toString();
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceRepository, 'updateOne')
|
||||||
|
.mockResolvedValue({ modifiedCount: 1 } as any);
|
||||||
|
jest
|
||||||
|
.spyOn(surveyMetaRepository, 'updateMany')
|
||||||
|
.mockResolvedValue({ modifiedCount: 1 } as any);
|
||||||
|
|
||||||
|
await service.delete(workspaceId);
|
||||||
|
|
||||||
|
expect(workspaceRepository.updateOne).toHaveBeenCalledTimes(1);
|
||||||
|
|
||||||
|
expect(surveyMetaRepository.updateMany).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,183 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { WorkspaceMemberController } from '../controllers/workspaceMember.controller';
|
||||||
|
import { WorkspaceMemberService } from '../services/workspaceMember.service';
|
||||||
|
import { CreateWorkspaceMemberDto } from '../dto/createWorkspaceMember.dto';
|
||||||
|
import { UpdateWorkspaceMemberDto } from '../dto/updateWorkspaceMember.dto';
|
||||||
|
import { DeleteWorkspaceMemberDto } from '../dto/deleteWorkspaceMember.dto';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
|
describe('WorkspaceMemberController', () => {
|
||||||
|
let controller: WorkspaceMemberController;
|
||||||
|
let workspaceMemberService: WorkspaceMemberService;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
controllers: [WorkspaceMemberController],
|
||||||
|
providers: [
|
||||||
|
{
|
||||||
|
provide: WorkspaceMemberService,
|
||||||
|
useValue: {
|
||||||
|
create: jest.fn(),
|
||||||
|
findAllByWorkspaceId: jest.fn(),
|
||||||
|
updateRole: jest.fn(),
|
||||||
|
deleteMember: jest.fn(),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
controller = module.get<WorkspaceMemberController>(
|
||||||
|
WorkspaceMemberController,
|
||||||
|
);
|
||||||
|
workspaceMemberService = module.get<WorkspaceMemberService>(
|
||||||
|
WorkspaceMemberService,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a workspace member and return memberId', async () => {
|
||||||
|
const createDto: CreateWorkspaceMemberDto = {
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
userId: 'userId',
|
||||||
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
};
|
||||||
|
const createdMember = { _id: 'memberId' };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'create')
|
||||||
|
.mockResolvedValue(createdMember as unknown as WorkspaceMember);
|
||||||
|
|
||||||
|
const result = await controller.create(createDto);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
memberId: createdMember._id,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.create).toHaveBeenCalledWith({
|
||||||
|
userId: createDto.userId,
|
||||||
|
workspaceId: createDto.workspaceId,
|
||||||
|
role: createDto.role,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const createDto: CreateWorkspaceMemberDto = {
|
||||||
|
workspaceId: '',
|
||||||
|
userId: '',
|
||||||
|
role: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(controller.create(createDto)).rejects.toThrow(HttpException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAll', () => {
|
||||||
|
it('should return a list of workspace members', async () => {
|
||||||
|
const req = { query: { workspaceId: 'workspaceId' } };
|
||||||
|
const members = [{ userId: 'userId1', role: 'USER' }];
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'findAllByWorkspaceId')
|
||||||
|
.mockResolvedValue(members as unknown as Array<WorkspaceMember>);
|
||||||
|
|
||||||
|
const result = await controller.findAll(req);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: members,
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.findAllByWorkspaceId).toHaveBeenCalledWith({
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if workspaceId is not provided', async () => {
|
||||||
|
const req = { query: {} };
|
||||||
|
|
||||||
|
await expect(controller.findAll(req)).rejects.toThrow(HttpException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateRole', () => {
|
||||||
|
it('should update the role of a workspace member and return modifiedCount', async () => {
|
||||||
|
const updateDto: UpdateWorkspaceMemberDto = {
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
userId: 'userId',
|
||||||
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
};
|
||||||
|
const updateResult = { modifiedCount: 1 };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'updateRole')
|
||||||
|
.mockResolvedValue(updateResult);
|
||||||
|
|
||||||
|
const result = await controller.updateRole(updateDto);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
modifiedCount: updateResult.modifiedCount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.updateRole).toHaveBeenCalledWith({
|
||||||
|
workspaceId: updateDto.workspaceId,
|
||||||
|
userId: updateDto.userId,
|
||||||
|
role: updateDto.role,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const updateDto: UpdateWorkspaceMemberDto = {
|
||||||
|
workspaceId: '',
|
||||||
|
userId: '',
|
||||||
|
role: '',
|
||||||
|
};
|
||||||
|
|
||||||
|
await expect(controller.updateRole(updateDto)).rejects.toThrow(
|
||||||
|
HttpException,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('delete', () => {
|
||||||
|
it('should delete a workspace member and return deletedCount', async () => {
|
||||||
|
const deleteDto: DeleteWorkspaceMemberDto = {
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
userId: 'userId',
|
||||||
|
};
|
||||||
|
const deleteResult = { acknowledged: true, deletedCount: 1 };
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(workspaceMemberService, 'deleteMember')
|
||||||
|
.mockResolvedValue(deleteResult);
|
||||||
|
|
||||||
|
const result = await controller.delete(deleteDto);
|
||||||
|
|
||||||
|
expect(result).toEqual({
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
deletedCount: deleteResult.deletedCount,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
expect(workspaceMemberService.deleteMember).toHaveBeenCalledWith(
|
||||||
|
deleteDto,
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw an exception if validation fails', async () => {
|
||||||
|
const deleteDto: DeleteWorkspaceMemberDto = {
|
||||||
|
userId: '',
|
||||||
|
workspaceId: '',
|
||||||
|
};
|
||||||
|
await expect(controller.delete(deleteDto)).rejects.toThrow(HttpException);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
@ -0,0 +1,196 @@
|
|||||||
|
import { Test, TestingModule } from '@nestjs/testing';
|
||||||
|
import { getRepositoryToken } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
|
||||||
|
import { WorkspaceMemberService } from '../services/workspaceMember.service';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
|
||||||
|
jest.mock('src/guards/authentication.guard');
|
||||||
|
jest.mock('src/guards/survey.guard');
|
||||||
|
jest.mock('src/guards/workspace.guard');
|
||||||
|
|
||||||
|
describe('WorkspaceMemberService', () => {
|
||||||
|
let service: WorkspaceMemberService;
|
||||||
|
let repository: MongoRepository<WorkspaceMember>;
|
||||||
|
|
||||||
|
beforeEach(async () => {
|
||||||
|
const module: TestingModule = await Test.createTestingModule({
|
||||||
|
providers: [
|
||||||
|
WorkspaceMemberService,
|
||||||
|
{
|
||||||
|
provide: getRepositoryToken(WorkspaceMember),
|
||||||
|
useClass: MongoRepository,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
}).compile();
|
||||||
|
|
||||||
|
service = module.get<WorkspaceMemberService>(WorkspaceMemberService);
|
||||||
|
repository = module.get<MongoRepository<WorkspaceMember>>(
|
||||||
|
getRepositoryToken(WorkspaceMember),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('create', () => {
|
||||||
|
it('should create a new workspace member', async () => {
|
||||||
|
const member = {
|
||||||
|
role: 'admin',
|
||||||
|
userId: 'userId',
|
||||||
|
workspaceId: 'workspaceId',
|
||||||
|
};
|
||||||
|
const createdMember = { ...member, _id: new ObjectId() };
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'create').mockReturnValue(createdMember as any);
|
||||||
|
jest.spyOn(repository, 'save').mockResolvedValue(createdMember as any);
|
||||||
|
|
||||||
|
const result = await service.create(member);
|
||||||
|
|
||||||
|
expect(result).toEqual(createdMember);
|
||||||
|
expect(repository.create).toHaveBeenCalledWith(member);
|
||||||
|
expect(repository.save).toHaveBeenCalledWith(createdMember);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchCreate', () => {
|
||||||
|
it('should batch create workspace members', async () => {
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
|
const members = [
|
||||||
|
{ userId: 'userId1', role: 'admin' },
|
||||||
|
{ userId: 'userId2', role: 'user' },
|
||||||
|
];
|
||||||
|
const dataToInsert = members.map((item) => ({ ...item, workspaceId }));
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'insertMany')
|
||||||
|
.mockResolvedValueOnce({ insertedCount: members.length } as any);
|
||||||
|
|
||||||
|
const result = await service.batchCreate({ workspaceId, members });
|
||||||
|
|
||||||
|
expect(result).toEqual({ insertedCount: members.length });
|
||||||
|
expect(repository.insertMany).toHaveBeenCalledWith(dataToInsert);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return insertedCount 0 if no members to insert', async () => {
|
||||||
|
const workspaceId = new ObjectId().toString();
|
||||||
|
const members = [];
|
||||||
|
|
||||||
|
const result = await service.batchCreate({ workspaceId, members });
|
||||||
|
|
||||||
|
expect(result).toEqual({ insertedCount: 0 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('batchUpdate', () => {
|
||||||
|
it('should batch update workspace members roles', async () => {
|
||||||
|
const idList = [new ObjectId().toString(), new ObjectId().toString()];
|
||||||
|
const role = 'user';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'updateMany')
|
||||||
|
.mockResolvedValue({ modifiedCount: idList.length } as any);
|
||||||
|
|
||||||
|
const result = await service.batchUpdate({ idList, role });
|
||||||
|
|
||||||
|
expect(result).toEqual({ modifiedCount: idList.length });
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should return modifiedCount 0 if no ids to update', async () => {
|
||||||
|
const idList = [];
|
||||||
|
const role = 'user';
|
||||||
|
|
||||||
|
const result = await service.batchUpdate({ idList, role });
|
||||||
|
|
||||||
|
expect(result).toEqual({ modifiedCount: 0 });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAllByUserId', () => {
|
||||||
|
it('should return all workspace members by userId', async () => {
|
||||||
|
const userId = 'userId';
|
||||||
|
const members = [
|
||||||
|
{ userId, workspaceId: 'workspaceId1' },
|
||||||
|
{ userId, workspaceId: 'workspaceId2' },
|
||||||
|
];
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'find').mockResolvedValue(members as any);
|
||||||
|
|
||||||
|
const result = await service.findAllByUserId({ userId });
|
||||||
|
|
||||||
|
expect(result).toEqual(members);
|
||||||
|
expect(repository.find).toHaveBeenCalledWith({ where: { userId } });
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findAllByWorkspaceId', () => {
|
||||||
|
it('should return all workspace members by workspaceId', async () => {
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
|
const members = [
|
||||||
|
{ userId: 'userId1', workspaceId },
|
||||||
|
{ userId: 'userId2', workspaceId },
|
||||||
|
];
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'find').mockResolvedValue(members as any);
|
||||||
|
|
||||||
|
const result = await service.findAllByWorkspaceId({ workspaceId });
|
||||||
|
|
||||||
|
expect(result).toEqual(members);
|
||||||
|
expect(repository.find).toHaveBeenCalledTimes(1);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('findOne', () => {
|
||||||
|
it('should return a single workspace member', async () => {
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
|
const userId = 'userId';
|
||||||
|
const member = { userId, workspaceId };
|
||||||
|
|
||||||
|
jest.spyOn(repository, 'findOne').mockResolvedValue(member as any);
|
||||||
|
|
||||||
|
const result = await service.findOne({ workspaceId, userId });
|
||||||
|
|
||||||
|
expect(result).toEqual(member);
|
||||||
|
expect(repository.findOne).toHaveBeenCalledWith({
|
||||||
|
where: { workspaceId, userId },
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('updateRole', () => {
|
||||||
|
it('should update the role of a workspace member', async () => {
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
|
const userId = 'userId';
|
||||||
|
const role = 'admin';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'updateOne')
|
||||||
|
.mockResolvedValue({ modifiedCount: 1 } as any);
|
||||||
|
|
||||||
|
const result = await service.updateRole({ workspaceId, userId, role });
|
||||||
|
|
||||||
|
expect(result).toEqual({ modifiedCount: 1 });
|
||||||
|
expect(repository.updateOne).toHaveBeenCalledWith(
|
||||||
|
{ workspaceId, userId },
|
||||||
|
{ $set: { role } },
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('deleteMember', () => {
|
||||||
|
it('should delete a workspace member', async () => {
|
||||||
|
const workspaceId = 'workspaceId';
|
||||||
|
const userId = 'userId';
|
||||||
|
|
||||||
|
jest
|
||||||
|
.spyOn(repository, 'deleteOne')
|
||||||
|
.mockResolvedValue({ deletedCount: 1 } as any);
|
||||||
|
|
||||||
|
const result = await service.deleteMember({ workspaceId, userId });
|
||||||
|
|
||||||
|
expect(result).toEqual({ deletedCount: 1 });
|
||||||
|
expect(repository.deleteOne).toHaveBeenCalledWith({
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
329
server/src/modules/workspace/controllers/workspace.controller.ts
Normal file
329
server/src/modules/workspace/controllers/workspace.controller.ts
Normal file
@ -0,0 +1,329 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Delete,
|
||||||
|
Body,
|
||||||
|
Param,
|
||||||
|
UseGuards,
|
||||||
|
Request,
|
||||||
|
SetMetadata,
|
||||||
|
HttpCode,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
import moment from 'moment';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
|
|
||||||
|
import { WorkspaceService } from '../services/workspace.service';
|
||||||
|
import { WorkspaceMemberService } from '../services/workspaceMember.service';
|
||||||
|
|
||||||
|
import { CreateWorkspaceDto } from '../dto/createWorkspace.dto';
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import {
|
||||||
|
ROLE as WORKSPACE_ROLE,
|
||||||
|
PERMISSION as WORKSPACE_PERMISSION,
|
||||||
|
ROLE_PERMISSION as WORKSPACE_ROLE_PERMISSION,
|
||||||
|
} from 'src/enums/workspace';
|
||||||
|
import { splitMembers } from '../utils/splitMember';
|
||||||
|
import { UserService } from 'src/modules/auth/services/user.service';
|
||||||
|
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||||
|
import { Logger } from 'src/logger';
|
||||||
|
|
||||||
|
@ApiTags('workspace')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
@Controller('/api/workspace')
|
||||||
|
export class WorkspaceController {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceService: WorkspaceService,
|
||||||
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
|
private readonly userService: UserService,
|
||||||
|
private readonly surveyMetaService: SurveyMetaService,
|
||||||
|
private readonly logger: Logger,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Get('getRoleList')
|
||||||
|
@HttpCode(200)
|
||||||
|
async getRoleList() {
|
||||||
|
const rolePermissions = Object.values(WORKSPACE_ROLE_PERMISSION);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: rolePermissions,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@HttpCode(200)
|
||||||
|
async create(@Body() workspace: CreateWorkspaceDto, @Request() req) {
|
||||||
|
const { value, error } = CreateWorkspaceDto.validate(workspace);
|
||||||
|
if (error) {
|
||||||
|
this.logger.error(
|
||||||
|
`CreateWorkspaceDto validate failed: ${error.message}`,
|
||||||
|
{ req },
|
||||||
|
);
|
||||||
|
throw new HttpException(
|
||||||
|
`参数错误: 请联系管理员`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value.members) && value.members.length > 0) {
|
||||||
|
// 校验用户是否真实存在
|
||||||
|
const userIdList = value.members.map((item) => item.userId);
|
||||||
|
// 不能有重复的userId
|
||||||
|
const userIdSet = new Set(userIdList);
|
||||||
|
if (userIdList.length !== Array.from(userIdSet).length) {
|
||||||
|
throw new HttpException(
|
||||||
|
'不能重复添加用户',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const userList = await this.userService.getUserListByIds({
|
||||||
|
idList: userIdList,
|
||||||
|
});
|
||||||
|
const userInfoMap = userList.reduce((pre, cur) => {
|
||||||
|
const id = cur._id.toString();
|
||||||
|
pre[id] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
for (const member of value.members) {
|
||||||
|
if (!userInfoMap[member.userId]) {
|
||||||
|
throw new HttpException(
|
||||||
|
`用户id: {${member.userId}} 不存在`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
const userId = req.user._id.toString();
|
||||||
|
// 插入空间表
|
||||||
|
const retWorkspace = await this.workspaceService.create({
|
||||||
|
name: value.name,
|
||||||
|
description: value.description,
|
||||||
|
ownerId: userId,
|
||||||
|
});
|
||||||
|
const workspaceId = retWorkspace._id.toString();
|
||||||
|
// 空间的成员表要新增一条管理员数据
|
||||||
|
await this.workspaceMemberService.create({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
});
|
||||||
|
if (Array.isArray(value.members) && value.members.length > 0) {
|
||||||
|
await this.workspaceMemberService.batchCreate({
|
||||||
|
workspaceId,
|
||||||
|
members: value.members,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
workspaceId,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@HttpCode(200)
|
||||||
|
async findAll(@Request() req) {
|
||||||
|
const userId = req.user._id.toString();
|
||||||
|
// 查询当前用户参与的空间
|
||||||
|
const workspaceInfoList = await this.workspaceMemberService.findAllByUserId(
|
||||||
|
{ userId },
|
||||||
|
);
|
||||||
|
const workspaceIdList = workspaceInfoList.map((item) => item.workspaceId);
|
||||||
|
const workspaceInfoMap = workspaceInfoList.reduce((pre, cur) => {
|
||||||
|
pre[cur.workspaceId] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
// 查询当前用户的空间列表
|
||||||
|
const list = await this.workspaceService.findAllById({ workspaceIdList });
|
||||||
|
const ownerIdList = list.map((item) => item.ownerId);
|
||||||
|
const userList = await this.userService.getUserListByIds({
|
||||||
|
idList: ownerIdList,
|
||||||
|
});
|
||||||
|
const userInfoMap = userList.reduce((pre, cur) => {
|
||||||
|
const id = cur._id.toString();
|
||||||
|
pre[id] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
const surveyTotalList = await Promise.all(
|
||||||
|
workspaceIdList.map((item) => {
|
||||||
|
return this.surveyMetaService.countSurveyMetaByWorkspaceId({
|
||||||
|
workspaceId: item,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const surveyTotalMap = workspaceIdList.reduce((pre, cur, index) => {
|
||||||
|
const total = surveyTotalList[index];
|
||||||
|
pre[cur] = total;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
const memberTotalList = await Promise.all(
|
||||||
|
workspaceIdList.map((item) => {
|
||||||
|
return this.workspaceMemberService.countByWorkspaceId({
|
||||||
|
workspaceId: item,
|
||||||
|
});
|
||||||
|
}),
|
||||||
|
);
|
||||||
|
const memberTotalMap = workspaceIdList.reduce((pre, cur, index) => {
|
||||||
|
const total = memberTotalList[index];
|
||||||
|
pre[cur] = total;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
list: list.map((item) => {
|
||||||
|
const workspaceId = item._id.toString();
|
||||||
|
const curWorkspaceInfo = workspaceInfoMap?.[workspaceId] || {};
|
||||||
|
const ownerInfo = userInfoMap?.[item.ownerId] || {};
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
createDate: moment(item.createDate).format('YYYY-MM-DD HH:mm:ss'),
|
||||||
|
owner: ownerInfo.username,
|
||||||
|
currentUserId: curWorkspaceInfo.userId,
|
||||||
|
currentUserRole: curWorkspaceInfo.role,
|
||||||
|
surveyTotal: surveyTotalMap[workspaceId] || 0,
|
||||||
|
memberTotal: memberTotalMap[workspaceId] || 0,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get(':id')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(WorkspaceGuard)
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.READ_WORKSPACE])
|
||||||
|
@SetMetadata('workspaceId', 'params.id')
|
||||||
|
async getWorkspaceInfo(@Param('id') workspaceId: string, @Request() req) {
|
||||||
|
const workspaceInfo = await this.workspaceService.findOneById(workspaceId);
|
||||||
|
const members = await this.workspaceMemberService.findAllByWorkspaceId({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
const memberInfoMap = members.reduce((pre, cur) => {
|
||||||
|
cur[cur.userId] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
const userIdList = members.map((item) => item.userId);
|
||||||
|
const userList = await this.userService.getUserListByIds({
|
||||||
|
idList: userIdList,
|
||||||
|
});
|
||||||
|
const userInfoMap = userList.reduce((pre, cur) => {
|
||||||
|
const id = cur._id.toString();
|
||||||
|
pre[id] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
const currentUserId = req.user._id.toString();
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
_id: workspaceInfo._id,
|
||||||
|
name: workspaceInfo.name,
|
||||||
|
description: workspaceInfo.description,
|
||||||
|
currentUserId,
|
||||||
|
currentUserRole: memberInfoMap?.[currentUserId]?.role,
|
||||||
|
members: members.map((item) => {
|
||||||
|
return {
|
||||||
|
_id: item._id,
|
||||||
|
userId: item.userId,
|
||||||
|
role: item.role,
|
||||||
|
username: userInfoMap[item.userId].username,
|
||||||
|
};
|
||||||
|
}),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post(':id')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(WorkspaceGuard)
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])
|
||||||
|
@SetMetadata('workspaceId', 'params.id')
|
||||||
|
async update(@Param('id') id: string, @Body() workspace: CreateWorkspaceDto) {
|
||||||
|
const members = workspace.members;
|
||||||
|
if (!Array.isArray(members) || members.length === 0) {
|
||||||
|
throw new HttpException('成员不能为空', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
delete workspace.members;
|
||||||
|
const updateRes = await this.workspaceService.update(id, workspace);
|
||||||
|
this.logger.info(`updateRes: ${JSON.stringify(updateRes)}`);
|
||||||
|
const { newMembers, adminMembers, userMembers } = splitMembers(members);
|
||||||
|
if (
|
||||||
|
adminMembers.length === 0 &&
|
||||||
|
!newMembers.some((item) => item.role === WORKSPACE_ROLE.ADMIN)
|
||||||
|
) {
|
||||||
|
throw new HttpException(
|
||||||
|
'空间不能没有管理员',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const allUserIdList = members.map((item) => item.userId);
|
||||||
|
// 不能有重复的userId
|
||||||
|
const allUserIdSet = new Set(allUserIdList);
|
||||||
|
if (allUserIdList.length !== Array.from(allUserIdSet).length) {
|
||||||
|
throw new HttpException(
|
||||||
|
'不能重复添加用户',
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// 检查所有成员是否真实存在
|
||||||
|
const allUserList = await this.userService.getUserListByIds({
|
||||||
|
idList: allUserIdList,
|
||||||
|
});
|
||||||
|
const allUserInfoMap = allUserList.reduce((pre, cur) => {
|
||||||
|
const id = cur._id.toString();
|
||||||
|
pre[id] = cur;
|
||||||
|
return pre;
|
||||||
|
}, {});
|
||||||
|
for (const member of members) {
|
||||||
|
if (!allUserInfoMap[member.userId]) {
|
||||||
|
throw new HttpException(
|
||||||
|
`用户id: {${member.userId}} 不存在`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const allIds = [...adminMembers, ...userMembers];
|
||||||
|
// 新增和更新成员,把数据库里已删除的成员删掉
|
||||||
|
const res = await Promise.all([
|
||||||
|
this.workspaceMemberService.batchDelete({ idList: [], neIdList: allIds }),
|
||||||
|
this.workspaceMemberService.batchCreate({
|
||||||
|
workspaceId: id,
|
||||||
|
members: newMembers,
|
||||||
|
}),
|
||||||
|
this.workspaceMemberService.batchUpdate({
|
||||||
|
idList: adminMembers,
|
||||||
|
role: WORKSPACE_ROLE.ADMIN,
|
||||||
|
}),
|
||||||
|
this.workspaceMemberService.batchUpdate({
|
||||||
|
idList: userMembers,
|
||||||
|
role: WORKSPACE_ROLE.USER,
|
||||||
|
}),
|
||||||
|
]);
|
||||||
|
this.logger.info(`updateRes: ${JSON.stringify(res)}`);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Delete(':id')
|
||||||
|
@HttpCode(200)
|
||||||
|
@UseGuards(WorkspaceGuard)
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_WORKSPACE])
|
||||||
|
@SetMetadata('workspaceId', 'params.id')
|
||||||
|
async delete(@Param('id') id: string) {
|
||||||
|
const res = await this.workspaceService.delete(id);
|
||||||
|
this.logger.info(`res: ${JSON.stringify(res)}`);
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,120 @@
|
|||||||
|
import {
|
||||||
|
Controller,
|
||||||
|
Get,
|
||||||
|
Post,
|
||||||
|
Body,
|
||||||
|
UseGuards,
|
||||||
|
Request,
|
||||||
|
SetMetadata,
|
||||||
|
} from '@nestjs/common';
|
||||||
|
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||||
|
|
||||||
|
import { Authentication } from 'src/guards/authentication.guard';
|
||||||
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
|
|
||||||
|
import { WorkspaceMemberService } from '../services/workspaceMember.service';
|
||||||
|
|
||||||
|
import { CreateWorkspaceMemberDto } from '../dto/createWorkspaceMember.dto';
|
||||||
|
import { UpdateWorkspaceMemberDto } from '../dto/updateWorkspaceMember.dto';
|
||||||
|
import { DeleteWorkspaceMemberDto } from '../dto/deleteWorkspaceMember.dto';
|
||||||
|
|
||||||
|
import { HttpException } from 'src/exceptions/httpException';
|
||||||
|
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||||
|
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
@ApiTags('workspaceMember')
|
||||||
|
@ApiBearerAuth()
|
||||||
|
@UseGuards(WorkspaceGuard)
|
||||||
|
@UseGuards(Authentication)
|
||||||
|
@Controller('/api/workspaceMember')
|
||||||
|
export class WorkspaceMemberController {
|
||||||
|
constructor(
|
||||||
|
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
@Post()
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_MEMBER])
|
||||||
|
@SetMetadata('workspaceId', 'body.workspaceId')
|
||||||
|
async create(@Body() member: CreateWorkspaceMemberDto) {
|
||||||
|
const { error, value } = CreateWorkspaceMemberDto.validate(member);
|
||||||
|
if (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
`参数错误: ${error.message}`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const { workspaceId, role, userId } = value;
|
||||||
|
const res = await this.workspaceMemberService.create({
|
||||||
|
userId,
|
||||||
|
workspaceId,
|
||||||
|
role,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
memberId: res._id.toString(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Get()
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.READ_MEMBER])
|
||||||
|
@SetMetadata('workspaceId', 'query.workspaceId')
|
||||||
|
async findAll(@Request() req) {
|
||||||
|
const workspaceId = req.query.workspaceId;
|
||||||
|
if (!workspaceId) {
|
||||||
|
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||||
|
}
|
||||||
|
const list = await this.workspaceMemberService.findAllByWorkspaceId({
|
||||||
|
workspaceId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: list,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('updateRole')
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_MEMBER])
|
||||||
|
@SetMetadata('workspaceId', 'body.workspaceId')
|
||||||
|
async updateRole(@Body() updateDto: UpdateWorkspaceMemberDto) {
|
||||||
|
const { error, value } = UpdateWorkspaceMemberDto.validate(updateDto);
|
||||||
|
if (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
`参数错误: ${error.message}`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const updateRes = await this.workspaceMemberService.updateRole({
|
||||||
|
role: value.role,
|
||||||
|
workspaceId: value.workspaceId,
|
||||||
|
userId: value.userId,
|
||||||
|
});
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
modifiedCount: updateRes.modifiedCount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
@Post('deleteMember')
|
||||||
|
@SetMetadata('workspacePermissions', [WORKSPACE_PERMISSION.WRITE_MEMBER])
|
||||||
|
@SetMetadata('workspaceId', 'body.id')
|
||||||
|
async delete(@Body() deleteDto: DeleteWorkspaceMemberDto) {
|
||||||
|
const { value, error } = DeleteWorkspaceMemberDto.validate(deleteDto);
|
||||||
|
if (error) {
|
||||||
|
throw new HttpException(
|
||||||
|
`参数错误: ${error.message}`,
|
||||||
|
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
const res = await this.workspaceMemberService.deleteMember({ ...value });
|
||||||
|
return {
|
||||||
|
code: 200,
|
||||||
|
data: {
|
||||||
|
deletedCount: res.deletedCount,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
29
server/src/modules/workspace/dto/createWorkspace.dto.ts
Normal file
29
server/src/modules/workspace/dto/createWorkspace.dto.ts
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
export class CreateWorkspaceDto {
|
||||||
|
@ApiProperty({ description: '空间名称', required: true })
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '空间描述', required: false })
|
||||||
|
description?: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '空间成员', required: true })
|
||||||
|
members: Array<{ userId: string; role: WORKSPACE_ROLE; _id?: string }>;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
name: Joi.string().required(),
|
||||||
|
description: Joi.string().allow(null, ''),
|
||||||
|
members: Joi.array()
|
||||||
|
.allow(null)
|
||||||
|
.items(
|
||||||
|
Joi.object({
|
||||||
|
userId: Joi.string().required(),
|
||||||
|
role: Joi.string().valid(WORKSPACE_ROLE.ADMIN, WORKSPACE_ROLE.USER),
|
||||||
|
}),
|
||||||
|
),
|
||||||
|
}).validate(data, { allowUnknown: true });
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
export class CreateWorkspaceMemberDto {
|
||||||
|
@ApiProperty({ description: '空间角色', required: true })
|
||||||
|
role: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '空间id', required: false })
|
||||||
|
workspaceId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户id', required: true })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
role: Joi.string()
|
||||||
|
.valid(WORKSPACE_ROLE.ADMIN, WORKSPACE_ROLE.USER)
|
||||||
|
.required(),
|
||||||
|
workspaceId: Joi.string().required(),
|
||||||
|
userId: Joi.string().required(),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,17 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
|
||||||
|
export class DeleteWorkspaceMemberDto {
|
||||||
|
@ApiProperty({ description: '空间id', required: false })
|
||||||
|
workspaceId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户id', required: false })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
workspaceId: Joi.string().required(),
|
||||||
|
userId: Joi.string().required(),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,24 @@
|
|||||||
|
import { ApiProperty } from '@nestjs/swagger';
|
||||||
|
import Joi from 'joi';
|
||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
|
||||||
|
export class UpdateWorkspaceMemberDto {
|
||||||
|
@ApiProperty({ description: '空间角色', required: true })
|
||||||
|
role: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '空间id', required: false })
|
||||||
|
workspaceId: string;
|
||||||
|
|
||||||
|
@ApiProperty({ description: '用户id', required: false })
|
||||||
|
userId: string;
|
||||||
|
|
||||||
|
static validate(data) {
|
||||||
|
return Joi.object({
|
||||||
|
role: Joi.string()
|
||||||
|
.valid(WORKSPACE_ROLE.ADMIN, WORKSPACE_ROLE.USER)
|
||||||
|
.required(),
|
||||||
|
workspaceId: Joi.string().required(),
|
||||||
|
userId: Joi.string().required(),
|
||||||
|
}).validate(data);
|
||||||
|
}
|
||||||
|
}
|
107
server/src/modules/workspace/services/workspace.service.ts
Normal file
107
server/src/modules/workspace/services/workspace.service.ts
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
|
||||||
|
import { Workspace } from 'src/models/workspace.entity';
|
||||||
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(Workspace)
|
||||||
|
private workspaceRepository: MongoRepository<Workspace>,
|
||||||
|
@InjectRepository(SurveyMeta)
|
||||||
|
private surveyMetaRepository: MongoRepository<SurveyMeta>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(workspace: {
|
||||||
|
name: string;
|
||||||
|
description: string;
|
||||||
|
ownerId: string;
|
||||||
|
}): Promise<Workspace> {
|
||||||
|
const newWorkspace = this.workspaceRepository.create({
|
||||||
|
...workspace,
|
||||||
|
});
|
||||||
|
return this.workspaceRepository.save(newWorkspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOneById(id) {
|
||||||
|
return this.workspaceRepository.findOne({
|
||||||
|
where: {
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllById({
|
||||||
|
workspaceIdList,
|
||||||
|
}: {
|
||||||
|
workspaceIdList: string[];
|
||||||
|
}): Promise<Workspace[]> {
|
||||||
|
return this.workspaceRepository.find({
|
||||||
|
where: {
|
||||||
|
_id: {
|
||||||
|
$in: workspaceIdList.map((item) => new ObjectId(item)),
|
||||||
|
},
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
order: {
|
||||||
|
_id: -1,
|
||||||
|
},
|
||||||
|
select: [
|
||||||
|
'_id',
|
||||||
|
'curStatus',
|
||||||
|
'name',
|
||||||
|
'description',
|
||||||
|
'ownerId',
|
||||||
|
'createDate',
|
||||||
|
],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
update(id: string, workspace: Partial<Workspace>) {
|
||||||
|
return this.workspaceRepository.update(id, workspace);
|
||||||
|
}
|
||||||
|
|
||||||
|
async delete(id: string) {
|
||||||
|
const newStatus = {
|
||||||
|
status: RECORD_STATUS.REMOVED,
|
||||||
|
date: Date.now(),
|
||||||
|
};
|
||||||
|
const workspaceRes = await this.workspaceRepository.updateOne(
|
||||||
|
{
|
||||||
|
_id: new ObjectId(id),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
curStatus: newStatus,
|
||||||
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: newStatus as never,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
const surveyRes = await this.surveyMetaRepository.updateMany(
|
||||||
|
{
|
||||||
|
workspaceId: id,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
curStatus: newStatus,
|
||||||
|
},
|
||||||
|
$push: {
|
||||||
|
statusList: newStatus as never,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
return {
|
||||||
|
workspaceRes,
|
||||||
|
surveyRes,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
143
server/src/modules/workspace/services/workspaceMember.service.ts
Normal file
143
server/src/modules/workspace/services/workspaceMember.service.ts
Normal file
@ -0,0 +1,143 @@
|
|||||||
|
import { Injectable } from '@nestjs/common';
|
||||||
|
import { InjectRepository } from '@nestjs/typeorm';
|
||||||
|
import { MongoRepository } from 'typeorm';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
import { ObjectId } from 'mongodb';
|
||||||
|
import { RECORD_STATUS } from 'src/enums';
|
||||||
|
|
||||||
|
@Injectable()
|
||||||
|
export class WorkspaceMemberService {
|
||||||
|
constructor(
|
||||||
|
@InjectRepository(WorkspaceMember)
|
||||||
|
private workspaceMemberRepository: MongoRepository<WorkspaceMember>,
|
||||||
|
) {}
|
||||||
|
|
||||||
|
async create(member: {
|
||||||
|
role: string;
|
||||||
|
userId: string;
|
||||||
|
workspaceId: string;
|
||||||
|
}): Promise<WorkspaceMember> {
|
||||||
|
const newMember = this.workspaceMemberRepository.create(member);
|
||||||
|
return this.workspaceMemberRepository.save(newMember);
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchCreate({
|
||||||
|
workspaceId,
|
||||||
|
members,
|
||||||
|
}: {
|
||||||
|
workspaceId: string;
|
||||||
|
members: Array<{ userId: string; role: string }>;
|
||||||
|
}) {
|
||||||
|
if (members.length === 0) {
|
||||||
|
return {
|
||||||
|
insertedCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
const dataToInsert = members.map((item) => {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
workspaceId,
|
||||||
|
};
|
||||||
|
});
|
||||||
|
return this.workspaceMemberRepository.insertMany(dataToInsert);
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchUpdate({ idList, role }: { idList: Array<string>; role: string }) {
|
||||||
|
if (idList.length === 0) {
|
||||||
|
return {
|
||||||
|
modifiedCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.workspaceMemberRepository.updateMany(
|
||||||
|
{
|
||||||
|
_id: {
|
||||||
|
$in: idList.map((item) => new ObjectId(item)),
|
||||||
|
},
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async batchDelete({
|
||||||
|
idList,
|
||||||
|
neIdList,
|
||||||
|
}: {
|
||||||
|
idList: Array<string>;
|
||||||
|
neIdList: Array<string>;
|
||||||
|
}) {
|
||||||
|
if (idList.length === 0 || neIdList.length === 0) {
|
||||||
|
return {
|
||||||
|
modifiedCount: 0,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
return this.workspaceMemberRepository.deleteMany({
|
||||||
|
_id: {
|
||||||
|
$in: idList.map((item) => new ObjectId(item)),
|
||||||
|
$nin: neIdList.map((item) => new ObjectId(item)),
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllByUserId({ userId }): Promise<WorkspaceMember[]> {
|
||||||
|
return this.workspaceMemberRepository.find({
|
||||||
|
where: {
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findAllByWorkspaceId({ workspaceId }): Promise<WorkspaceMember[]> {
|
||||||
|
return this.workspaceMemberRepository.find({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
select: ['_id', 'createDate', 'curStatus', 'role', 'userId'],
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async findOne({ workspaceId, userId }): Promise<WorkspaceMember> {
|
||||||
|
return this.workspaceMemberRepository.findOne({
|
||||||
|
where: {
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async updateRole({ workspaceId, userId, role }) {
|
||||||
|
return this.workspaceMemberRepository.updateOne(
|
||||||
|
{
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
$set: {
|
||||||
|
role,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
async deleteMember({ workspaceId, userId }) {
|
||||||
|
return this.workspaceMemberRepository.deleteOne({
|
||||||
|
workspaceId,
|
||||||
|
userId,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async countByWorkspaceId({ workspaceId }) {
|
||||||
|
return this.workspaceMemberRepository.count({
|
||||||
|
workspaceId,
|
||||||
|
'curStatus.status': {
|
||||||
|
$ne: RECORD_STATUS.REMOVED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
26
server/src/modules/workspace/utils/splitMember.ts
Normal file
26
server/src/modules/workspace/utils/splitMember.ts
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
import { ROLE as WORKSPACE_ROLE } from 'src/enums/workspace';
|
||||||
|
export type Member = {
|
||||||
|
userId: string;
|
||||||
|
role: string;
|
||||||
|
_id?: string;
|
||||||
|
};
|
||||||
|
|
||||||
|
export const splitMembers = (members: Array<Member>) => {
|
||||||
|
const newMembers = [],
|
||||||
|
adminMembers = [],
|
||||||
|
userMembers = [];
|
||||||
|
for (const member of members) {
|
||||||
|
if (!member._id) {
|
||||||
|
newMembers.push(member);
|
||||||
|
} else if (member.role === WORKSPACE_ROLE.ADMIN) {
|
||||||
|
adminMembers.push(member._id);
|
||||||
|
} else {
|
||||||
|
userMembers.push(member._id);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
newMembers,
|
||||||
|
adminMembers,
|
||||||
|
userMembers,
|
||||||
|
};
|
||||||
|
};
|
38
server/src/modules/workspace/workspace.module.ts
Normal file
38
server/src/modules/workspace/workspace.module.ts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
import { Module } from '@nestjs/common';
|
||||||
|
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||||
|
import { ConfigModule } from '@nestjs/config';
|
||||||
|
|
||||||
|
import { WorkspaceService } from './services/workspace.service';
|
||||||
|
import { WorkspaceMemberService } from './services/workspaceMember.service';
|
||||||
|
import { SurveyMetaService } from '../survey/services/surveyMeta.service';
|
||||||
|
|
||||||
|
import { WorkspaceController } from './controllers/workspace.controller';
|
||||||
|
|
||||||
|
import { Workspace } from 'src/models/workspace.entity';
|
||||||
|
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||||
|
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||||
|
|
||||||
|
import { AuthModule } from '../auth/auth.module';
|
||||||
|
|
||||||
|
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||||
|
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||||
|
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||||
|
|
||||||
|
@Module({
|
||||||
|
imports: [
|
||||||
|
TypeOrmModule.forFeature([Workspace, WorkspaceMember, SurveyMeta]),
|
||||||
|
ConfigModule,
|
||||||
|
AuthModule,
|
||||||
|
],
|
||||||
|
controllers: [WorkspaceController],
|
||||||
|
providers: [
|
||||||
|
WorkspaceService,
|
||||||
|
WorkspaceMemberService,
|
||||||
|
LoggerProvider,
|
||||||
|
WorkspaceGuard,
|
||||||
|
SurveyMetaService,
|
||||||
|
PluginManagerProvider,
|
||||||
|
],
|
||||||
|
exports: [WorkspaceMemberService],
|
||||||
|
})
|
||||||
|
export class WorkspaceModule {}
|
Loading…
Reference in New Issue
Block a user