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 { MessageModule } from './modules/message/message.module';
|
||||
import { FileModule } from './modules/file/file.module';
|
||||
import { WorkspaceModule } from './modules/workspace/workspace.module';
|
||||
|
||||
import { join } from 'path';
|
||||
|
||||
@ -31,6 +32,9 @@ import { ClientEncrypt } from './models/clientEncrypt.entity';
|
||||
import { Word } from './models/word.entity';
|
||||
import { MessagePushingTask } from './models/messagePushingTask.entity';
|
||||
import { MessagePushingLog } from './models/messagePushingLog.entity';
|
||||
import { WorkspaceMember } from './models/workspaceMember.entity';
|
||||
import { Workspace } from './models/workspace.entity';
|
||||
import { Collaborator } from './models/collaborator.entity';
|
||||
|
||||
import { LoggerProvider } from './logger/logger.provider';
|
||||
import { PluginManagerProvider } from './securityPlugin/pluginManager.provider';
|
||||
@ -74,6 +78,9 @@ import { Logger } from './logger';
|
||||
Word,
|
||||
MessagePushingTask,
|
||||
MessagePushingLog,
|
||||
Workspace,
|
||||
WorkspaceMember,
|
||||
Collaborator,
|
||||
],
|
||||
};
|
||||
},
|
||||
@ -92,6 +99,7 @@ import { Logger } from './logger';
|
||||
}),
|
||||
MessageModule,
|
||||
FileModule,
|
||||
WorkspaceModule,
|
||||
],
|
||||
controllers: [AppController],
|
||||
providers: [
|
||||
|
@ -1,6 +1,8 @@
|
||||
export enum EXCEPTION_CODE {
|
||||
AUTHENTICATION_FAILED = 1001, // 没有权限
|
||||
AUTHENTICATION_FAILED = 1001, // 未授权
|
||||
PARAMETER_ERROR = 1002, // 参数有误
|
||||
NO_PERMISSION = 1003, // 没有操作权限
|
||||
|
||||
USER_EXISTS = 2001, // 用户已存在
|
||||
USER_NOT_EXISTS = 2002, // 用户不存在
|
||||
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 {
|
||||
ExceptionFilter,
|
||||
Catch,
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { HttpException } from './httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
|
||||
export class NoSurveyPermissionException extends HttpException {
|
||||
export class NoPermissionException extends HttpException {
|
||||
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 { ConfigService } from '@nestjs/config';
|
||||
import { Authtication } from './authtication';
|
||||
import { Authentication } from '../authentication.guard';
|
||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||
import { AuthenticationException } from 'src/exceptions/authException';
|
||||
import { User } from 'src/models/user.entity';
|
||||
|
||||
jest.mock('jsonwebtoken');
|
||||
|
||||
describe('Authtication', () => {
|
||||
let guard: Authtication;
|
||||
describe('Authentication', () => {
|
||||
let guard: Authentication;
|
||||
let authService: AuthService;
|
||||
let configService: ConfigService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
providers: [
|
||||
Authtication,
|
||||
Authentication,
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: {
|
||||
@ -31,7 +31,7 @@ describe('Authtication', () => {
|
||||
],
|
||||
}).compile();
|
||||
|
||||
guard = module.get<Authtication>(Authtication);
|
||||
guard = module.get<Authentication>(Authentication);
|
||||
authService = module.get<AuthService>(AuthService);
|
||||
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';
|
||||
|
||||
@Injectable()
|
||||
export class Authtication implements CanActivate {
|
||||
export class Authentication implements CanActivate {
|
||||
constructor(private readonly authService: AuthService) {}
|
||||
|
||||
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 }) {
|
||||
const datetime = moment().format('YYYY-MM-DD HH:mm:ss.SSS');
|
||||
const level = options.level;
|
||||
const dltag = options.dltag ? `${options.dltag}||` : '';
|
||||
const traceIdStr = options?.req['traceId']
|
||||
? `traceid=${options?.req['traceId']}||`
|
||||
const level = options?.level;
|
||||
const dltag = options?.dltag ? `${options.dltag}||` : '';
|
||||
const traceIdStr = options?.req?.['traceId']
|
||||
? `traceid=${options?.req?.['traceId']}||`
|
||||
: '';
|
||||
return log4jsLogger[level](
|
||||
`[${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()
|
||||
owner: string;
|
||||
|
||||
@Column()
|
||||
ownerId: string;
|
||||
|
||||
@Column()
|
||||
createMethod: string;
|
||||
|
||||
@Column()
|
||||
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 { HttpException } from 'src/exceptions/httpException';
|
||||
import { hash256 } from 'src/utils/hash256';
|
||||
import { RECORD_STATUS } from 'src/enums';
|
||||
|
||||
describe('UserService', () => {
|
||||
let service: UserService;
|
||||
@ -135,7 +136,10 @@ describe('UserService', () => {
|
||||
const user = await service.getUserByUsername(username);
|
||||
|
||||
expect(userRepository.findOne).toHaveBeenCalledWith({
|
||||
where: { username: username },
|
||||
where: {
|
||||
'curStatus.status': { $ne: RECORD_STATUS.REMOVED },
|
||||
username: username,
|
||||
},
|
||||
});
|
||||
expect(user).toEqual(userInfo);
|
||||
});
|
||||
|
@ -4,6 +4,7 @@ import { AuthService } from './services/auth.service';
|
||||
import { CaptchaService } from './services/captcha.service';
|
||||
|
||||
import { AuthController } from './controllers/auth.controller';
|
||||
import { UserController } from './controllers/user.controller';
|
||||
|
||||
import { User } from 'src/models/user.entity';
|
||||
import { Captcha } from 'src/models/captcha.entity';
|
||||
@ -13,7 +14,7 @@ import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [TypeOrmModule.forFeature([User, Captcha]), ConfigModule],
|
||||
controllers: [AuthController],
|
||||
controllers: [AuthController, UserController],
|
||||
providers: [UserService, AuthService, CaptchaService],
|
||||
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 { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { hash256 } from 'src/utils/hash256';
|
||||
import { RECORD_STATUS } from 'src/enums';
|
||||
import { ObjectId } from 'mongodb';
|
||||
|
||||
@Injectable()
|
||||
export class UserService {
|
||||
@ -51,9 +53,55 @@ export class UserService {
|
||||
const user = await this.userRepository.findOne({
|
||||
where: {
|
||||
username: username,
|
||||
'curStatus.status': {
|
||||
$ne: RECORD_STATUS.REMOVED,
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
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,
|
||||
} from '@nestjs/common';
|
||||
import { FileInterceptor } from '@nestjs/platform-express';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { FileService } from '../services/file.service';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||
import { AuthenticationException } from 'src/exceptions/authException';
|
||||
import { ConfigService } from '@nestjs/config';
|
||||
|
||||
@ApiTags('file')
|
||||
@Controller('/api/file')
|
||||
export class FileController {
|
||||
constructor(
|
||||
|
@ -10,7 +10,7 @@ import {
|
||||
MESSAGE_PUSHING_TYPE,
|
||||
} from 'src/enums/messagePushing';
|
||||
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 { UpdateMessagePushingTaskDto } from '../dto/updateMessagePushingTask.dto';
|
||||
@ -38,7 +38,7 @@ describe('MessagePushingTaskController', () => {
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: Authtication,
|
||||
provide: Authentication,
|
||||
useClass: jest.fn().mockImplementation(() => ({
|
||||
canActivate: () => true,
|
||||
})),
|
||||
|
@ -25,9 +25,9 @@ import { QueryMessagePushingTaskListDto } from '../dto/queryMessagePushingTaskLi
|
||||
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
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()
|
||||
@ApiTags('messagePushingTasks')
|
||||
@Controller('/api/messagePushingTasks')
|
||||
@ -47,12 +47,10 @@ export class MessagePushingTaskController {
|
||||
req,
|
||||
@Body() createMessagePushingTaskDto: CreateMessagePushingTaskDto,
|
||||
) {
|
||||
let data;
|
||||
try {
|
||||
data = await CreateMessagePushingTaskDto.validate(
|
||||
const { error, value } = CreateMessagePushingTaskDto.validate(
|
||||
createMessagePushingTaskDto,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error) {
|
||||
throw new HttpException(
|
||||
`参数错误: ${error.message}`,
|
||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||
@ -61,7 +59,7 @@ export class MessagePushingTaskController {
|
||||
const userId = req.user._id;
|
||||
|
||||
const messagePushingTask = await this.messagePushingTaskService.create({
|
||||
...data,
|
||||
...value,
|
||||
ownerId: userId,
|
||||
});
|
||||
return {
|
||||
@ -83,10 +81,8 @@ export class MessagePushingTaskController {
|
||||
req,
|
||||
@Query() query: QueryMessagePushingTaskListDto,
|
||||
) {
|
||||
let data;
|
||||
try {
|
||||
data = await QueryMessagePushingTaskListDto.validate(query);
|
||||
} catch (error) {
|
||||
const { error, value } = QueryMessagePushingTaskListDto.validate(query);
|
||||
if (error) {
|
||||
throw new HttpException(
|
||||
`参数错误: ${error.message}`,
|
||||
EXCEPTION_CODE.PARAMETER_ERROR,
|
||||
@ -94,8 +90,8 @@ export class MessagePushingTaskController {
|
||||
}
|
||||
const userId = req.user._id;
|
||||
const list = await this.messagePushingTaskService.findAll({
|
||||
surveyId: data.surveyId,
|
||||
hook: data.triggerHook,
|
||||
surveyId: value.surveyId,
|
||||
hook: value.triggerHook,
|
||||
ownerId: userId,
|
||||
});
|
||||
return {
|
||||
|
@ -29,8 +29,8 @@ export class CreateMessagePushingTaskDto {
|
||||
})
|
||||
surveys?: string[];
|
||||
|
||||
static async validate(data) {
|
||||
return await Joi.object({
|
||||
static validate(data) {
|
||||
return Joi.object({
|
||||
name: Joi.string().required(),
|
||||
type: Joi.string().allow(null).default(MESSAGE_PUSHING_TYPE.HTTP),
|
||||
pushAddress: Joi.string().required(),
|
||||
@ -38,6 +38,6 @@ export class CreateMessagePushingTaskDto {
|
||||
.allow(null)
|
||||
.default(MESSAGE_PUSHING_HOOK.RESPONSE_INSERTED),
|
||||
surveys: Joi.array().items(Joi.string()).allow(null).default([]),
|
||||
}).validateAsync(data);
|
||||
}).validate(data);
|
||||
}
|
||||
}
|
||||
|
@ -13,6 +13,6 @@ export class QueryMessagePushingTaskListDto {
|
||||
return Joi.object({
|
||||
surveyId: 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 { 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 { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugin';
|
||||
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('../../surveyResponse/services/responseScheme.service');
|
||||
|
||||
jest.mock('src/guards/authentication.guard');
|
||||
jest.mock('src/guards/survey.guard');
|
||||
jest.mock('src/guards/workspace.guard');
|
||||
|
||||
describe('DataStatisticController', () => {
|
||||
let controller: DataStatisticController;
|
||||
let dataStatisticService: DataStatisticService;
|
||||
let surveyMetaService: SurveyMetaService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -32,12 +36,6 @@ describe('DataStatisticController', () => {
|
||||
ResponseSchemaService,
|
||||
PluginManagerProvider,
|
||||
ConfigService,
|
||||
{
|
||||
provide: Authtication,
|
||||
useClass: jest.fn().mockImplementation(() => ({
|
||||
canActivate: () => true,
|
||||
})),
|
||||
},
|
||||
{
|
||||
provide: UserService,
|
||||
useClass: jest.fn().mockImplementation(() => ({
|
||||
@ -54,13 +52,18 @@ describe('DataStatisticController', () => {
|
||||
},
|
||||
})),
|
||||
},
|
||||
{
|
||||
provide: Logger,
|
||||
useValue: {
|
||||
error: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<DataStatisticController>(DataStatisticController);
|
||||
dataStatisticService =
|
||||
module.get<DataStatisticService>(DataStatisticService);
|
||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
||||
const pluginManager = module.get<XiaojuSurveyPluginManager>(
|
||||
XiaojuSurveyPluginManager,
|
||||
);
|
||||
@ -101,9 +104,6 @@ describe('DataStatisticController', () => {
|
||||
],
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValueOnce(undefined);
|
||||
jest
|
||||
.spyOn(controller['responseSchemaService'], 'getResponseSchemaByPageId')
|
||||
.mockResolvedValueOnce({} as any);
|
||||
@ -111,7 +111,7 @@ describe('DataStatisticController', () => {
|
||||
.spyOn(dataStatisticService, 'getDataTable')
|
||||
.mockResolvedValueOnce(mockDataTable);
|
||||
|
||||
const result = await controller.data(mockRequest.query, mockRequest);
|
||||
const result = await controller.data(mockRequest.query, {});
|
||||
|
||||
expect(result).toEqual({
|
||||
code: 200,
|
||||
@ -146,10 +146,6 @@ describe('DataStatisticController', () => {
|
||||
{ difTime: '0.5', createDate: '2024-02-11', data123: '13800000000' },
|
||||
],
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValueOnce(undefined);
|
||||
jest
|
||||
.spyOn(controller['responseSchemaService'], 'getResponseSchemaByPageId')
|
||||
.mockResolvedValueOnce({} as any);
|
||||
@ -157,7 +153,7 @@ describe('DataStatisticController', () => {
|
||||
.spyOn(dataStatisticService, 'getDataTable')
|
||||
.mockResolvedValueOnce(mockDataTable);
|
||||
|
||||
const result = await controller.data(mockRequest.query, mockRequest);
|
||||
const result = await controller.data(mockRequest.query, {});
|
||||
|
||||
expect(result).toEqual({
|
||||
code: 200,
|
||||
|
@ -18,7 +18,9 @@ jest.mock('../../surveyResponse/services/responseScheme.service');
|
||||
jest.mock('../services/contentSecurity.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', () => {
|
||||
let controller: SurveyController;
|
||||
@ -98,7 +100,7 @@ describe('SurveyController', () => {
|
||||
);
|
||||
|
||||
const result = await controller.createSurvey(surveyInfo, {
|
||||
user: { username: 'testUser' },
|
||||
user: { username: 'testUser', _id: new ObjectId() },
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -123,9 +125,6 @@ describe('SurveyController', () => {
|
||||
createMethod: 'copy',
|
||||
createFrom: existsSurveyId.toString(),
|
||||
};
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValue(Promise.resolve(existsSurveyMeta));
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'createSurveyMeta')
|
||||
@ -136,7 +135,10 @@ describe('SurveyController', () => {
|
||||
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);
|
||||
expect(result?.data?.id).toBeDefined();
|
||||
});
|
||||
@ -151,9 +153,6 @@ describe('SurveyController', () => {
|
||||
owner: 'testUser',
|
||||
} as SurveyMeta;
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
||||
jest
|
||||
.spyOn(surveyConfService, 'saveSurveyConf')
|
||||
.mockResolvedValue(undefined);
|
||||
@ -183,6 +182,7 @@ describe('SurveyController', () => {
|
||||
|
||||
const result = await controller.updateConf(reqBody, {
|
||||
user: { username: 'testUser', _id: 'testUserId' },
|
||||
surveyMeta,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -200,9 +200,6 @@ describe('SurveyController', () => {
|
||||
owner: 'testUser',
|
||||
} as SurveyMeta;
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'deleteSurveyMeta')
|
||||
.mockResolvedValue(undefined);
|
||||
@ -210,10 +207,10 @@ describe('SurveyController', () => {
|
||||
.spyOn(responseSchemaService, 'deleteResponseSchema')
|
||||
.mockResolvedValue(undefined);
|
||||
|
||||
const result = await controller.deleteSurvey(
|
||||
{ surveyId: surveyId.toString() },
|
||||
{ user: { username: 'testUser' } },
|
||||
);
|
||||
const result = await controller.deleteSurvey({
|
||||
user: { username: 'testUser' },
|
||||
surveyMeta,
|
||||
});
|
||||
|
||||
expect(result).toEqual({
|
||||
code: 200,
|
||||
@ -230,10 +227,6 @@ describe('SurveyController', () => {
|
||||
owner: 'testUser',
|
||||
} as SurveyMeta;
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
||||
|
||||
jest
|
||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||
.mockResolvedValue(
|
||||
@ -243,7 +236,10 @@ describe('SurveyController', () => {
|
||||
} as SurveyConf),
|
||||
);
|
||||
|
||||
const request = { user: { username: 'testUser' } };
|
||||
const request = {
|
||||
user: { username: 'testUser', _id: new ObjectId() },
|
||||
surveyMeta,
|
||||
};
|
||||
const result = await controller.getSurvey(
|
||||
{ surveyId: surveyId.toString() },
|
||||
request,
|
||||
@ -262,10 +258,6 @@ describe('SurveyController', () => {
|
||||
owner: 'testUser',
|
||||
} as SurveyMeta;
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
||||
|
||||
jest
|
||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||
.mockResolvedValue(
|
||||
@ -296,7 +288,7 @@ describe('SurveyController', () => {
|
||||
|
||||
const result = await controller.publishSurvey(
|
||||
{ surveyId: surveyId.toString() },
|
||||
{ user: { username: 'testUser', _id: 'testUserId' } },
|
||||
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
||||
);
|
||||
|
||||
expect(result).toEqual({
|
||||
@ -312,10 +304,6 @@ describe('SurveyController', () => {
|
||||
owner: 'testUser',
|
||||
} as SurveyMeta;
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockResolvedValue(Promise.resolve(surveyMeta));
|
||||
|
||||
jest
|
||||
.spyOn(surveyConfService, 'getSurveyConfBySurveyId')
|
||||
.mockResolvedValue(
|
||||
@ -338,7 +326,7 @@ describe('SurveyController', () => {
|
||||
await expect(
|
||||
controller.publishSurvey(
|
||||
{ surveyId: surveyId.toString() },
|
||||
{ user: { username: 'testUser', _id: 'testUserId' } },
|
||||
{ user: { username: 'testUser', _id: 'testUserId' }, surveyMeta },
|
||||
),
|
||||
).rejects.toThrow(
|
||||
new HttpException(
|
||||
|
@ -6,13 +6,16 @@ import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.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 { Logger } from 'src/logger';
|
||||
|
||||
jest.mock('src/guards/authentication.guard');
|
||||
jest.mock('src/guards/survey.guard');
|
||||
jest.mock('src/guards/workspace.guard');
|
||||
|
||||
describe('SurveyHistoryController', () => {
|
||||
let controller: SurveyHistoryController;
|
||||
let surveyHistoryService: SurveyHistoryService;
|
||||
let surveyMetaService: SurveyMetaService;
|
||||
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
@ -25,18 +28,6 @@ describe('SurveyHistoryController', () => {
|
||||
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,
|
||||
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();
|
||||
|
||||
controller = module.get<SurveyHistoryController>(SurveyHistoryController);
|
||||
surveyHistoryService =
|
||||
module.get<SurveyHistoryService>(SurveyHistoryService);
|
||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
||||
});
|
||||
|
||||
it('should return history list when query is valid', async () => {
|
||||
const req = { user: { username: 'testUser' } };
|
||||
const queryInfo = { surveyId: 'survey123', historyType: 'published' };
|
||||
|
||||
await controller.getList(queryInfo, req);
|
||||
|
||||
expect(surveyMetaService.checkSurveyAccess).toHaveBeenCalledWith({
|
||||
surveyId: queryInfo.surveyId,
|
||||
username: req.user.username,
|
||||
});
|
||||
await controller.getList(queryInfo, {});
|
||||
|
||||
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledWith({
|
||||
surveyId: queryInfo.surveyId,
|
||||
@ -79,6 +74,5 @@ describe('SurveyHistoryController', () => {
|
||||
});
|
||||
|
||||
expect(surveyHistoryService.getHistoryList).toHaveBeenCalledTimes(1);
|
||||
expect(surveyMetaService.checkSurveyAccess).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
@ -1,11 +1,15 @@
|
||||
import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SurveyMetaController } from '../controllers/surveyMeta.controller';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||
import { LoggerProvider } from 'src/logger/logger.provider';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
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', () => {
|
||||
let controller: SurveyMetaController;
|
||||
@ -18,7 +22,6 @@ describe('SurveyMetaController', () => {
|
||||
{
|
||||
provide: SurveyMetaService,
|
||||
useValue: {
|
||||
checkSurveyAccess: jest.fn().mockResolvedValue({}),
|
||||
editSurveyMeta: jest.fn().mockResolvedValue(undefined),
|
||||
getSurveyMetaList: jest
|
||||
.fn()
|
||||
@ -26,13 +29,14 @@ describe('SurveyMetaController', () => {
|
||||
},
|
||||
},
|
||||
LoggerProvider,
|
||||
{
|
||||
provide: CollaboratorService,
|
||||
useValue: {
|
||||
getCollaboratorListByUserId: jest.fn().mockResolvedValue([]),
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
.overrideGuard(Authtication)
|
||||
.useValue({
|
||||
canActivate: () => true,
|
||||
})
|
||||
.compile();
|
||||
}).compile();
|
||||
|
||||
controller = module.get<SurveyMetaController>(SurveyMetaController);
|
||||
surveyMetaService = module.get<SurveyMetaService>(SurveyMetaService);
|
||||
@ -44,30 +48,21 @@ describe('SurveyMetaController', () => {
|
||||
title: 'Test title',
|
||||
surveyId: 'test-survey-id',
|
||||
};
|
||||
const req = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
},
|
||||
};
|
||||
|
||||
const survey = {
|
||||
title: '',
|
||||
remark: '',
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'checkSurveyAccess')
|
||||
.mockImplementation(() => {
|
||||
return Promise.resolve(survey) as Promise<SurveyMeta>;
|
||||
});
|
||||
const req = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
},
|
||||
surveyMeta: survey,
|
||||
};
|
||||
|
||||
const result = await controller.updateMeta(reqBody, req);
|
||||
|
||||
expect(surveyMetaService.checkSurveyAccess).toHaveBeenCalledWith({
|
||||
surveyId: reqBody.surveyId,
|
||||
username: req.user.username,
|
||||
});
|
||||
|
||||
expect(surveyMetaService.editSurveyMeta).toHaveBeenCalledWith({
|
||||
title: reqBody.title,
|
||||
remark: reqBody.remark,
|
||||
@ -91,7 +86,6 @@ describe('SurveyMetaController', () => {
|
||||
expect(error.code).toBe(EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
expect(surveyMetaService.checkSurveyAccess).not.toHaveBeenCalled();
|
||||
expect(surveyMetaService.editSurveyMeta).not.toHaveBeenCalled();
|
||||
});
|
||||
|
||||
@ -100,13 +94,14 @@ describe('SurveyMetaController', () => {
|
||||
curPage: 1,
|
||||
pageSize: 10,
|
||||
};
|
||||
const userId = new ObjectId().toString();
|
||||
const req = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
_id: new ObjectId(userId),
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
jest
|
||||
.spyOn(surveyMetaService, 'getSurveyMetaList')
|
||||
.mockImplementation(() => {
|
||||
@ -115,7 +110,7 @@ describe('SurveyMetaController', () => {
|
||||
count: 10,
|
||||
data: [
|
||||
{
|
||||
id: '1',
|
||||
_id: new ObjectId(),
|
||||
createDate: date,
|
||||
updateDate: date,
|
||||
curStatus: {
|
||||
@ -155,10 +150,10 @@ describe('SurveyMetaController', () => {
|
||||
username: req.user.username,
|
||||
filter: {},
|
||||
order: {},
|
||||
surveyIdList: [],
|
||||
userId,
|
||||
workspaceId: undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
|
||||
it('should get survey meta list with filter and order', async () => {
|
||||
@ -177,13 +172,14 @@ describe('SurveyMetaController', () => {
|
||||
]),
|
||||
order: JSON.stringify([{ field: 'createDate', value: -1 }]),
|
||||
};
|
||||
const userId = new ObjectId().toString();
|
||||
const req = {
|
||||
user: {
|
||||
username: 'test-user',
|
||||
_id: new ObjectId(userId),
|
||||
},
|
||||
};
|
||||
|
||||
try {
|
||||
const result = await controller.getList(queryInfo, req);
|
||||
|
||||
expect(result.code).toEqual(200);
|
||||
@ -191,11 +187,11 @@ describe('SurveyMetaController', () => {
|
||||
pageNum: queryInfo.curPage,
|
||||
pageSize: queryInfo.pageSize,
|
||||
username: req.user.username,
|
||||
surveyIdList: [],
|
||||
userId,
|
||||
filter: { surveyType: 'normal', title: { $regex: 'hahah' } },
|
||||
order: { createDate: -1 },
|
||||
workspaceId: undefined,
|
||||
});
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -2,15 +2,13 @@ import { Test, TestingModule } from '@nestjs/testing';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
import { MongoRepository } from 'typeorm';
|
||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||
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 { getRepositoryToken } from '@nestjs/typeorm';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { SurveyUtilPlugin } from 'src/securityPlugin/surveyUtilPlugin';
|
||||
import { ObjectId } from 'mongodb';
|
||||
|
||||
describe('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', () => {
|
||||
it('should create a new survey meta and return it', async () => {
|
||||
const params = {
|
||||
@ -110,6 +62,7 @@ describe('SurveyMetaService', () => {
|
||||
remark: 'This is a test survey',
|
||||
surveyType: 'normal',
|
||||
username: 'testUser',
|
||||
userId: new ObjectId().toString(),
|
||||
createMethod: '',
|
||||
createFrom: '',
|
||||
};
|
||||
@ -133,6 +86,7 @@ describe('SurveyMetaService', () => {
|
||||
surveyType: params.surveyType,
|
||||
surveyPath: mockedSurveyPath,
|
||||
creator: params.username,
|
||||
ownerId: params.userId,
|
||||
owner: params.username,
|
||||
createMethod: params.createMethod,
|
||||
createFrom: params.createFrom,
|
||||
@ -213,6 +167,7 @@ describe('SurveyMetaService', () => {
|
||||
const condition = {
|
||||
pageNum: 1,
|
||||
pageSize: 10,
|
||||
userId: 'testUserId',
|
||||
username: 'testUser',
|
||||
filter: {},
|
||||
order: {},
|
||||
@ -222,15 +177,7 @@ describe('SurveyMetaService', () => {
|
||||
// 验证返回值
|
||||
expect(result).toEqual({ data: mockData, count: mockCount });
|
||||
// 验证repository方法被正确调用
|
||||
expect(surveyRepository.findAndCount).toHaveBeenCalledWith({
|
||||
where: {
|
||||
owner: 'testUser',
|
||||
'curStatus.status': { $ne: 'removed' },
|
||||
},
|
||||
skip: 0,
|
||||
take: 10,
|
||||
order: { createDate: -1 },
|
||||
});
|
||||
expect(surveyRepository.findAndCount).toHaveBeenCalledTimes(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,
|
||||
HttpCode,
|
||||
UseGuards,
|
||||
SetMetadata,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags, ApiBearerAuth } from '@nestjs/swagger';
|
||||
|
||||
import { DataStatisticService } from '../services/dataStatistic.service';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
import { ResponseSchemaService } from '../../surveyResponse/services/responseScheme.service';
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { Authentication } from 'src/guards/authentication.guard';
|
||||
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')
|
||||
@ApiBearerAuth()
|
||||
@Controller('/api/survey/dataStatistic')
|
||||
export class DataStatisticController {
|
||||
constructor(
|
||||
private readonly surveyMetaService: SurveyMetaService,
|
||||
private readonly responseSchemaService: ResponseSchemaService,
|
||||
private readonly dataStatisticService: DataStatisticService,
|
||||
private readonly pluginManager: XiaojuSurveyPluginManager,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Get('/dataTable')
|
||||
@HttpCode(200)
|
||||
@UseGuards(SurveyGuard)
|
||||
@SetMetadata('surveyId', 'query.surveyId')
|
||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_RESPONSE_MANAGE])
|
||||
@UseGuards(Authentication)
|
||||
async data(
|
||||
@Query()
|
||||
queryInfo,
|
||||
@Request()
|
||||
req,
|
||||
@Request() req,
|
||||
) {
|
||||
const validationResult = await Joi.object({
|
||||
const { value, error } = await Joi.object({
|
||||
surveyId: Joi.string().required(),
|
||||
isDesensitive: Joi.boolean().default(true), // 默认true就是需要脱敏
|
||||
page: Joi.number().default(1),
|
||||
pageSize: Joi.number().default(10),
|
||||
}).validateAsync(queryInfo);
|
||||
const { surveyId, isDesensitive, page, pageSize } = validationResult;
|
||||
const username = req.user.username;
|
||||
await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
}).validate(queryInfo);
|
||||
if (error) {
|
||||
this.logger.error(error.message, { req });
|
||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
const { surveyId, isDesensitive, page, pageSize } = value;
|
||||
const responseSchema =
|
||||
await this.responseSchemaService.getResponseSchemaByPageId(surveyId);
|
||||
const { total, listHead, listBody } =
|
||||
|
@ -7,7 +7,10 @@ import {
|
||||
HttpCode,
|
||||
UseGuards,
|
||||
Request,
|
||||
SetMetadata,
|
||||
} from '@nestjs/common';
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
import { SurveyConfService } from '../services/surveyConf.service';
|
||||
@ -16,14 +19,18 @@ import { ContentSecurityService } from '../services/contentSecurity.service';
|
||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||
|
||||
import BannerData from '../template/banner/index.json';
|
||||
import { CreateSurveyDto } from '../dto/createSurvey.dto';
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { Authentication } from 'src/guards/authentication.guard';
|
||||
import { HISTORY_TYPE } from 'src/enums';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
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')
|
||||
@Controller('/api/survey')
|
||||
@ -46,66 +53,57 @@ export class SurveyController {
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Post('/createSurvey')
|
||||
@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(
|
||||
@Body()
|
||||
reqBody,
|
||||
reqBody: CreateSurveyDto,
|
||||
@Request()
|
||||
req,
|
||||
) {
|
||||
let validationResult;
|
||||
try {
|
||||
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) {
|
||||
const { error, value } = CreateSurveyDto.validate(reqBody);
|
||||
if (error) {
|
||||
this.logger.error(`createSurvey_parameter error: ${error.message}`, {
|
||||
req,
|
||||
});
|
||||
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') {
|
||||
const survey = await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId: createFrom,
|
||||
username,
|
||||
});
|
||||
const survey = req.surveyMeta;
|
||||
surveyType = survey.surveyType;
|
||||
workspaceId = survey.workspaceId;
|
||||
} else {
|
||||
surveyType = validationResult.surveyType;
|
||||
surveyType = value.surveyType;
|
||||
workspaceId = value.workspaceId;
|
||||
}
|
||||
|
||||
const surveyMeta = await this.surveyMetaService.createSurveyMeta({
|
||||
title,
|
||||
remark,
|
||||
surveyType,
|
||||
username,
|
||||
username: req.user.username,
|
||||
userId: req.user._id.toString(),
|
||||
createMethod,
|
||||
createFrom,
|
||||
workspaceId,
|
||||
});
|
||||
await this.surveyConfService.createSurveyConf({
|
||||
surveyId: surveyMeta._id.toString(),
|
||||
surveyType: surveyType,
|
||||
createMethod: validationResult.createMethod,
|
||||
createFrom: validationResult.createFrom,
|
||||
createMethod: value.createMethod,
|
||||
createFrom: value.createFrom,
|
||||
});
|
||||
return {
|
||||
code: 200,
|
||||
@ -115,26 +113,30 @@ export class SurveyController {
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Post('/updateConf')
|
||||
@HttpCode(200)
|
||||
@UseGuards(SurveyGuard)
|
||||
@SetMetadata('surveyId', 'body.surveyId')
|
||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||
@UseGuards(Authentication)
|
||||
async updateConf(
|
||||
@Body()
|
||||
surveyInfo,
|
||||
@Request()
|
||||
req,
|
||||
) {
|
||||
const validationResult = await Joi.object({
|
||||
const { value, error } = Joi.object({
|
||||
surveyId: Joi.string().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 surveyId = validationResult.surveyId;
|
||||
await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
const configData = validationResult.configData;
|
||||
const surveyId = value.surveyId;
|
||||
|
||||
const configData = value.configData;
|
||||
await this.surveyConfService.saveSurveyConf({
|
||||
surveyId,
|
||||
schema: configData,
|
||||
@ -153,23 +155,18 @@ export class SurveyController {
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@HttpCode(200)
|
||||
@Post('/deleteSurvey')
|
||||
async deleteSurvey(@Body() reqBody, @Request() req) {
|
||||
const validationResult = await Joi.object({
|
||||
surveyId: Joi.string().required(),
|
||||
}).validateAsync(reqBody, { allowUnknown: true });
|
||||
const username = req.user.username;
|
||||
const surveyId = validationResult.surveyId;
|
||||
const survey = await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
@UseGuards(SurveyGuard)
|
||||
@SetMetadata('surveyId', 'body.surveyId')
|
||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||
@UseGuards(Authentication)
|
||||
async deleteSurvey(@Request() req) {
|
||||
const surveyMeta = req.surveyMeta;
|
||||
|
||||
await this.surveyMetaService.deleteSurveyMeta(survey);
|
||||
await this.surveyMetaService.deleteSurveyMeta(surveyMeta);
|
||||
await this.responseSchemaService.deleteResponseSchema({
|
||||
surveyPath: survey.surveyPath,
|
||||
surveyPath: surveyMeta.surveyPath,
|
||||
});
|
||||
|
||||
return {
|
||||
@ -177,9 +174,16 @@ export class SurveyController {
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Get('/getSurvey')
|
||||
@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(
|
||||
@Query()
|
||||
queryInfo: {
|
||||
@ -188,19 +192,28 @@ export class SurveyController {
|
||||
@Request()
|
||||
req,
|
||||
) {
|
||||
const validationResult = await Joi.object({
|
||||
const { value, error } = Joi.object({
|
||||
surveyId: Joi.string().required(),
|
||||
}).validateAsync(queryInfo);
|
||||
}).validate(queryInfo);
|
||||
|
||||
const username = req.user.username;
|
||||
const surveyId = validationResult.surveyId;
|
||||
const surveyMeta = await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
if (error) {
|
||||
this.logger.error(error.message, { req });
|
||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
const surveyId = value.surveyId;
|
||||
const surveyMeta = req.surveyMeta;
|
||||
const surveyConf =
|
||||
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 {
|
||||
code: 200,
|
||||
data: {
|
||||
@ -210,24 +223,28 @@ export class SurveyController {
|
||||
};
|
||||
}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Post('/publishSurvey')
|
||||
@HttpCode(200)
|
||||
@UseGuards(SurveyGuard)
|
||||
@SetMetadata('surveyId', 'body.surveyId')
|
||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||
@UseGuards(Authentication)
|
||||
async publishSurvey(
|
||||
@Body()
|
||||
surveyInfo,
|
||||
@Request()
|
||||
req,
|
||||
) {
|
||||
const validationResult = await Joi.object({
|
||||
const { value, error } = Joi.object({
|
||||
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 surveyId = validationResult.surveyId;
|
||||
const surveyMeta = await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
const surveyId = value.surveyId;
|
||||
const surveyMeta = req.surveyMeta;
|
||||
const surveyConf =
|
||||
await this.surveyConfService.getSurveyConfBySurveyId(surveyId);
|
||||
|
||||
|
@ -4,48 +4,59 @@ import {
|
||||
Query,
|
||||
HttpCode,
|
||||
UseGuards,
|
||||
SetMetadata,
|
||||
Request,
|
||||
} from '@nestjs/common';
|
||||
|
||||
import { SurveyHistoryService } from '../services/surveyHistory.service';
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
|
||||
import * as Joi from 'joi';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
|
||||
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')
|
||||
@Controller('/api/surveyHisotry')
|
||||
export class SurveyHistoryController {
|
||||
constructor(
|
||||
private readonly surveyHistoryService: SurveyHistoryService,
|
||||
private readonly surveyMetaService: SurveyMetaService,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Get('/getList')
|
||||
@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(
|
||||
@Query()
|
||||
queryInfo: {
|
||||
surveyId: string;
|
||||
historyType: string;
|
||||
},
|
||||
@Request()
|
||||
req,
|
||||
@Request() req,
|
||||
) {
|
||||
const validationResult = await Joi.object({
|
||||
const { value, error } = Joi.object({
|
||||
surveyId: Joi.string().required(),
|
||||
historyType: Joi.string().required(),
|
||||
}).validateAsync(queryInfo);
|
||||
}).validate(queryInfo);
|
||||
|
||||
const username = req.user.username;
|
||||
const surveyId = validationResult.surveyId;
|
||||
const historyType = validationResult.historyType;
|
||||
await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
if (error) {
|
||||
this.logger.error(error.message, { req });
|
||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
const surveyId = value.surveyId;
|
||||
const historyType = value.historyType;
|
||||
const data = await this.surveyHistoryService.getHistoryList({
|
||||
surveyId,
|
||||
historyType,
|
||||
|
@ -7,18 +7,26 @@ import {
|
||||
HttpCode,
|
||||
UseGuards,
|
||||
Request,
|
||||
SetMetadata,
|
||||
} from '@nestjs/common';
|
||||
import * as Joi from 'joi';
|
||||
import moment from 'moment';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
|
||||
import { SurveyMetaService } from '../services/surveyMeta.service';
|
||||
|
||||
import { getFilter, getOrder } from 'src/utils/surveyUtil';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { Authtication } from 'src/guards/authtication';
|
||||
import { Authentication } from 'src/guards/authentication.guard';
|
||||
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')
|
||||
@Controller('/api/survey')
|
||||
@ -26,34 +34,31 @@ export class SurveyMetaController {
|
||||
constructor(
|
||||
private readonly surveyMetaService: SurveyMetaService,
|
||||
private readonly logger: Logger,
|
||||
private readonly collaboratorService: CollaboratorService,
|
||||
) {}
|
||||
|
||||
@UseGuards(Authtication)
|
||||
@Post('/updateMeta')
|
||||
@HttpCode(200)
|
||||
@UseGuards(SurveyGuard)
|
||||
@SetMetadata('surveyId', 'body.surveyId')
|
||||
@SetMetadata('surveyPermission', [SURVEY_PERMISSION.SURVEY_CONF_MANAGE])
|
||||
@UseGuards(Authentication)
|
||||
async updateMeta(@Body() reqBody, @Request() req) {
|
||||
let validationResult;
|
||||
try {
|
||||
validationResult = await Joi.object({
|
||||
const { value, error } = Joi.object({
|
||||
title: Joi.string().required(),
|
||||
remark: Joi.string().allow(null, '').default(''),
|
||||
surveyId: Joi.string().required(),
|
||||
}).validateAsync(reqBody, { allowUnknown: true });
|
||||
} catch (error) {
|
||||
}).validate(reqBody, { allowUnknown: true });
|
||||
|
||||
if (error) {
|
||||
this.logger.error(`updateMeta_parameter error: ${error.message}`, {
|
||||
req,
|
||||
});
|
||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
const username = req.user.username;
|
||||
const surveyId = validationResult.surveyId;
|
||||
const survey = await this.surveyMetaService.checkSurveyAccess({
|
||||
surveyId,
|
||||
username,
|
||||
});
|
||||
survey.title = validationResult.title;
|
||||
survey.remark = validationResult.remark;
|
||||
const survey = req.surveyMeta;
|
||||
survey.title = value.title;
|
||||
survey.remark = value.remark;
|
||||
|
||||
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')
|
||||
@HttpCode(200)
|
||||
async getList(
|
||||
@Query()
|
||||
queryInfo: {
|
||||
curPage: number;
|
||||
pageSize: number;
|
||||
},
|
||||
queryInfo: GetSurveyListDto,
|
||||
@Request()
|
||||
req,
|
||||
) {
|
||||
const validationResult = await Joi.object({
|
||||
curPage: Joi.number().required(),
|
||||
pageSize: Joi.number().allow(null).default(10),
|
||||
filter: Joi.string().allow(null),
|
||||
order: Joi.string().allow(null),
|
||||
}).validateAsync(queryInfo);
|
||||
const { curPage, pageSize } = validationResult;
|
||||
const { value, error } = GetSurveyListDto.validate(queryInfo);
|
||||
if (error) {
|
||||
this.logger.error(error.message, { req });
|
||||
throw new HttpException('参数有误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
const { curPage, pageSize, workspaceId } = value;
|
||||
let filter = {},
|
||||
order = {};
|
||||
if (validationResult.filter) {
|
||||
if (value.filter) {
|
||||
try {
|
||||
filter = getFilter(
|
||||
JSON.parse(decodeURIComponent(validationResult.filter)),
|
||||
);
|
||||
filter = getFilter(JSON.parse(decodeURIComponent(value.filter)));
|
||||
} catch (error) {
|
||||
console.log(error);
|
||||
this.logger.error(error.message, { req });
|
||||
}
|
||||
}
|
||||
if (validationResult.order) {
|
||||
if (value.order) {
|
||||
try {
|
||||
order = order = getOrder(
|
||||
JSON.parse(decodeURIComponent(validationResult.order)),
|
||||
);
|
||||
order = order = getOrder(JSON.parse(decodeURIComponent(value.order)));
|
||||
} 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 data = await this.surveyMetaService.getSurveyMetaList({
|
||||
pageNum: curPage,
|
||||
pageSize: pageSize,
|
||||
userId,
|
||||
username,
|
||||
filter,
|
||||
order,
|
||||
workspaceId,
|
||||
surveyIdList,
|
||||
});
|
||||
return {
|
||||
code: 200,
|
||||
@ -121,6 +132,15 @@ export class SurveyMetaController {
|
||||
item.createDate = moment(item.createDate).format(fmt);
|
||||
item.updateDate = moment(item.updateDate).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;
|
||||
}),
|
||||
},
|
||||
|
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 { RECORD_STATUS } from 'src/enums';
|
||||
import { ObjectId } from 'mongodb';
|
||||
import { NoSurveyPermissionException } from 'src/exceptions/noSurveyPermissionException';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { XiaojuSurveyPluginManager } from 'src/securityPlugin/pluginManager';
|
||||
|
||||
@ -34,18 +32,10 @@ export class SurveyMetaService {
|
||||
return surveyPath;
|
||||
}
|
||||
|
||||
async checkSurveyAccess({ surveyId, username }) {
|
||||
const survey = await this.surveyRepository.findOne({
|
||||
async getSurveyById({ surveyId }) {
|
||||
return this.surveyRepository.findOne({
|
||||
where: { _id: new ObjectId(surveyId) },
|
||||
});
|
||||
|
||||
if (!survey) {
|
||||
throw new SurveyNotFoundException('问卷不存在');
|
||||
}
|
||||
if (survey.owner !== username) {
|
||||
throw new NoSurveyPermissionException('没有权限');
|
||||
}
|
||||
return survey;
|
||||
}
|
||||
|
||||
async createSurveyMeta(params: {
|
||||
@ -53,11 +43,21 @@ export class SurveyMetaService {
|
||||
remark: string;
|
||||
surveyType: string;
|
||||
username: string;
|
||||
userId: string;
|
||||
createMethod: string;
|
||||
createFrom: string;
|
||||
workspaceId?: string;
|
||||
}) {
|
||||
const { title, remark, surveyType, username, createMethod, createFrom } =
|
||||
params;
|
||||
const {
|
||||
title,
|
||||
remark,
|
||||
surveyType,
|
||||
username,
|
||||
createMethod,
|
||||
createFrom,
|
||||
userId,
|
||||
workspaceId,
|
||||
} = params;
|
||||
const surveyPath = await this.getNewSurveyPath();
|
||||
const newSurvey = this.surveyRepository.create({
|
||||
title,
|
||||
@ -66,8 +66,10 @@ export class SurveyMetaService {
|
||||
surveyPath,
|
||||
creator: username,
|
||||
owner: username,
|
||||
ownerId: userId,
|
||||
createMethod,
|
||||
createFrom,
|
||||
workspaceId,
|
||||
});
|
||||
|
||||
return await this.surveyRepository.save(newSurvey);
|
||||
@ -112,22 +114,48 @@ export class SurveyMetaService {
|
||||
pageNum: number;
|
||||
pageSize: number;
|
||||
username: string;
|
||||
userId: string;
|
||||
filter: Record<string, any>;
|
||||
order: Record<string, any>;
|
||||
workspaceId?: string;
|
||||
surveyIdList?: Array<string>;
|
||||
}): Promise<{ data: any[]; count: number }> {
|
||||
const { pageNum, pageSize, username } = condition;
|
||||
const { pageNum, pageSize, userId, username, workspaceId, surveyIdList } =
|
||||
condition;
|
||||
const skip = (pageNum - 1) * pageSize;
|
||||
try {
|
||||
const query = Object.assign(
|
||||
const query: Record<string, any> = Object.assign(
|
||||
{},
|
||||
{
|
||||
owner: username,
|
||||
'curStatus.status': {
|
||||
$ne: 'removed',
|
||||
},
|
||||
},
|
||||
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 =
|
||||
condition.order && Object.keys(condition.order).length > 0
|
||||
? (condition.order as FindOptionsOrder<SurveyMeta>)
|
||||
@ -160,4 +188,14 @@ export class SurveyMetaService {
|
||||
}
|
||||
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 { AuthModule } from '../auth/auth.module';
|
||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||
|
||||
import { DataStatisticController } from './controllers/dataStatistic.controller';
|
||||
import { SurveyController } from './controllers/survey.controller';
|
||||
import { SurveyHistoryController } from './controllers/surveyHistory.controller';
|
||||
import { SurveyMetaController } from './controllers/surveyMeta.controller';
|
||||
import { SurveyUIController } from './controllers/surveyUI.controller';
|
||||
import { CollaboratorController } from './controllers/collaborator.controller';
|
||||
|
||||
import { SurveyConf } from 'src/models/surveyConf.entity';
|
||||
import { SurveyHistory } from 'src/models/surveyHistory.entity';
|
||||
import { SurveyMeta } from 'src/models/surveyMeta.entity';
|
||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||
import { Word } from 'src/models/word.entity';
|
||||
import { Collaborator } from 'src/models/collaborator.entity';
|
||||
import { PluginManagerProvider } from 'src/securityPlugin/pluginManager.provider';
|
||||
|
||||
import { DataStatisticService } from './services/dataStatistic.service';
|
||||
@ -25,6 +28,7 @@ import { SurveyConfService } from './services/surveyConf.service';
|
||||
import { SurveyHistoryService } from './services/surveyHistory.service';
|
||||
import { SurveyMetaService } from './services/surveyMeta.service';
|
||||
import { ContentSecurityService } from './services/contentSecurity.service';
|
||||
import { CollaboratorService } from './services/collaborator.service';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -34,10 +38,12 @@ import { ContentSecurityService } from './services/contentSecurity.service';
|
||||
SurveyHistory,
|
||||
SurveyResponse,
|
||||
Word,
|
||||
Collaborator,
|
||||
]),
|
||||
ConfigModule,
|
||||
SurveyResponseModule,
|
||||
AuthModule,
|
||||
WorkspaceModule,
|
||||
],
|
||||
controllers: [
|
||||
DataStatisticController,
|
||||
@ -45,6 +51,7 @@ import { ContentSecurityService } from './services/contentSecurity.service';
|
||||
SurveyHistoryController,
|
||||
SurveyMetaController,
|
||||
SurveyUIController,
|
||||
CollaboratorController,
|
||||
],
|
||||
providers: [
|
||||
DataStatisticService,
|
||||
@ -53,6 +60,7 @@ import { ContentSecurityService } from './services/contentSecurity.service';
|
||||
SurveyMetaService,
|
||||
PluginManagerProvider,
|
||||
ContentSecurityService,
|
||||
CollaboratorService,
|
||||
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 { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||
import { Logger } from 'src/logger';
|
||||
|
||||
const mockDecryptErrorBody = {
|
||||
surveyPath: 'EBzdmnSp',
|
||||
@ -116,6 +117,13 @@ describe('SurveyResponseController', () => {
|
||||
runResponseDataPush: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: Logger,
|
||||
useValue: {
|
||||
error: jest.fn(),
|
||||
info: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@ -197,7 +205,7 @@ describe('SurveyResponseController', () => {
|
||||
.spyOn(clientEncryptService, 'deleteEncryptInfo')
|
||||
.mockResolvedValueOnce(undefined);
|
||||
|
||||
const result = await controller.createResponse(reqBody);
|
||||
const result = await controller.createResponse(reqBody, {});
|
||||
|
||||
expect(result).toEqual({ code: 200, msg: '提交成功' });
|
||||
expect(
|
||||
@ -244,7 +252,7 @@ describe('SurveyResponseController', () => {
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValueOnce(null);
|
||||
|
||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||
SurveyNotFoundException,
|
||||
);
|
||||
});
|
||||
@ -253,7 +261,7 @@ describe('SurveyResponseController', () => {
|
||||
const reqBody = cloneDeep(mockSubmitData);
|
||||
delete reqBody.sign;
|
||||
|
||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||
HttpException,
|
||||
);
|
||||
|
||||
@ -266,7 +274,7 @@ describe('SurveyResponseController', () => {
|
||||
const reqBody = cloneDeep(mockDecryptErrorBody);
|
||||
reqBody.sign = 'mock sign';
|
||||
|
||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||
HttpException,
|
||||
);
|
||||
|
||||
@ -282,7 +290,7 @@ describe('SurveyResponseController', () => {
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValueOnce(mockResponseSchema);
|
||||
|
||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||
HttpException,
|
||||
);
|
||||
});
|
||||
@ -294,7 +302,7 @@ describe('SurveyResponseController', () => {
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValueOnce(mockResponseSchema);
|
||||
|
||||
await expect(controller.createResponse(reqBody)).rejects.toThrow(
|
||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||
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 { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||
import { checkSign } from 'src/utils/checkSign';
|
||||
@ -16,6 +16,7 @@ import moment from 'moment';
|
||||
import * as Joi from 'joi';
|
||||
import * as forge from 'node-forge';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Logger } from 'src/logger';
|
||||
|
||||
@ApiTags('surveyResponse')
|
||||
@Controller('/api/surveyResponse')
|
||||
@ -26,25 +27,33 @@ export class SurveyResponseController {
|
||||
private readonly surveyResponseService: SurveyResponseService,
|
||||
private readonly clientEncryptService: ClientEncryptService,
|
||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||
private readonly logger: Logger,
|
||||
) {}
|
||||
|
||||
@Post('/createResponse')
|
||||
@HttpCode(200)
|
||||
async createResponse(@Body() reqBody) {
|
||||
async createResponse(@Body() reqBody, @Request() req) {
|
||||
// 检查签名
|
||||
checkSign(reqBody);
|
||||
// 校验参数
|
||||
const validationResult = await Joi.object({
|
||||
const { value, error } = Joi.object({
|
||||
surveyPath: Joi.string().required(),
|
||||
data: Joi.any().required(),
|
||||
encryptType: Joi.string(),
|
||||
sessionId: Joi.string(),
|
||||
clientTime: Joi.number().required(),
|
||||
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 } =
|
||||
validationResult;
|
||||
value;
|
||||
|
||||
// 查询schema
|
||||
const responseSchema =
|
||||
@ -153,8 +162,8 @@ export class SurveyResponseController {
|
||||
|
||||
// 对用户提交的数据进行遍历处理
|
||||
for (const field in decryptedData) {
|
||||
const value = decryptedData[field];
|
||||
const values = Array.isArray(value) ? value : [value];
|
||||
const val = decryptedData[field];
|
||||
const vals = Array.isArray(val) ? val : [val];
|
||||
if (field in optionTextAndId) {
|
||||
// 记录选项的提交数量,用于投票题回显、或者拓展上限限制功能
|
||||
const optionCountData: Record<string, any> =
|
||||
@ -164,7 +173,7 @@ export class SurveyResponseController {
|
||||
type: 'option',
|
||||
})) || { total: 0 };
|
||||
optionCountData.total++;
|
||||
for (const val of values) {
|
||||
for (const val of vals) {
|
||||
if (!optionCountData[val]) {
|
||||
optionCountData[val] = 1;
|
||||
} else {
|
||||
@ -183,7 +192,7 @@ export class SurveyResponseController {
|
||||
// 入库
|
||||
const surveyResponse =
|
||||
await this.surveyResponseService.createSurveyResponse({
|
||||
surveyPath: validationResult.surveyPath,
|
||||
surveyPath: value.surveyPath,
|
||||
data: decryptedData,
|
||||
clientTime,
|
||||
difTime,
|
||||
|
@ -1,4 +1,6 @@
|
||||
import { Module } from '@nestjs/common';
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
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 { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||
import { ClientEncrypt } from 'src/models/clientEncrypt.entity';
|
||||
import { Logger } from 'src/logger';
|
||||
|
||||
import { ClientEncryptController } from './controllers/clientEncrpt.controller';
|
||||
import { CounterController } from './controllers/counter.controller';
|
||||
@ -18,9 +21,6 @@ import { ResponseSchemaController } from './controllers/responseSchema.controlle
|
||||
import { SurveyResponseController } from './controllers/surveyResponse.controller';
|
||||
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';
|
||||
|
||||
import { TypeOrmModule } from '@nestjs/typeorm';
|
||||
import { ConfigModule } from '@nestjs/config';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
TypeOrmModule.forFeature([
|
||||
@ -44,6 +44,7 @@ import { ConfigModule } from '@nestjs/config';
|
||||
SurveyResponseService,
|
||||
CounterService,
|
||||
ClientEncryptService,
|
||||
Logger,
|
||||
],
|
||||
exports: [
|
||||
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