feat: 白名单功能-服务端 (#357)
This commit is contained in:
parent
df6e14c585
commit
ee9a1ea9c7
@ -12,6 +12,7 @@ export enum EXCEPTION_CODE {
|
||||
SURVEY_NOT_FOUND = 3004, // 问卷不存在
|
||||
SURVEY_CONTENT_NOT_ALLOW = 3005, // 存在禁用内容
|
||||
CAPTCHA_INCORRECT = 4001, // 验证码不正确
|
||||
WHITELIST_ERROR = 4002, // 白名单校验错误
|
||||
|
||||
RESPONSE_SIGN_ERROR = 9001, // 签名不正确
|
||||
RESPONSE_CURRENT_TIME_NOT_ALLOW = 9002, // 当前时间不允许提交
|
||||
|
@ -94,6 +94,23 @@ export interface SubmitConf {
|
||||
msgContent: MsgContent;
|
||||
}
|
||||
|
||||
// 白名单类型
|
||||
export enum WhitelistType {
|
||||
ALL = 'ALL',
|
||||
// 空间成员
|
||||
MEMBER = 'MEMBER',
|
||||
// 自定义
|
||||
CUSTOM = 'CUSTOM',
|
||||
}
|
||||
|
||||
// 白名单用户类型
|
||||
export enum MemberType {
|
||||
// 手机号
|
||||
MOBILE = 'MOBILE',
|
||||
// 邮箱
|
||||
EMAIL = 'EMAIL',
|
||||
}
|
||||
|
||||
export interface BaseConf {
|
||||
begTime: string;
|
||||
endTime: string;
|
||||
@ -101,6 +118,18 @@ export interface BaseConf {
|
||||
answerEndTime: string;
|
||||
tLimit: number;
|
||||
language: string;
|
||||
// 访问密码开关
|
||||
passwordSwitch?: boolean;
|
||||
// 密码
|
||||
password?: string | null;
|
||||
// 白名单类型
|
||||
whitelistType?: WhitelistType;
|
||||
// 白名单用户类型
|
||||
memberType?: MemberType;
|
||||
// 白名单列表
|
||||
whitelist?: string[];
|
||||
// 提示语
|
||||
whitelistTip?: string;
|
||||
}
|
||||
|
||||
export interface SkinConf {
|
||||
|
@ -31,6 +31,7 @@ import { SURVEY_PERMISSION } from 'src/enums/surveyPermission';
|
||||
|
||||
import { WorkspaceGuard } from 'src/guards/workspace.guard';
|
||||
import { PERMISSION as WORKSPACE_PERMISSION } from 'src/enums/workspace';
|
||||
import { MemberType, WhitelistType } from 'src/interfaces/survey';
|
||||
|
||||
@ApiTags('survey')
|
||||
@Controller('/api/survey')
|
||||
@ -214,6 +215,16 @@ export class SurveyController {
|
||||
surveyMeta.isCollaborated = false;
|
||||
}
|
||||
|
||||
// 白名单相关字段的默认值
|
||||
const baseConf = surveyConf.code?.baseConf;
|
||||
if (baseConf) {
|
||||
baseConf.passwordSwitch = baseConf.passwordSwitch ?? false;
|
||||
baseConf.password = baseConf.password ?? '';
|
||||
baseConf.whitelistType = baseConf.whitelistType ?? WhitelistType.ALL;
|
||||
baseConf.whitelist = baseConf.whitelist ?? [];
|
||||
baseConf.memberType = baseConf.memberType ?? MemberType.MOBILE;
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: {
|
||||
|
@ -6,6 +6,11 @@ import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { RECORD_STATUS } from 'src/enums';
|
||||
|
||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||
import { Logger } from 'src/logger';
|
||||
import { UserService } from 'src/modules/auth/services/user.service';
|
||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||
import { AuthService } from 'src/modules/auth/services/auth.service';
|
||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||
|
||||
jest.mock('../services/responseScheme.service');
|
||||
|
||||
@ -16,7 +21,40 @@ describe('ResponseSchemaController', () => {
|
||||
beforeEach(async () => {
|
||||
const module: TestingModule = await Test.createTestingModule({
|
||||
controllers: [ResponseSchemaController],
|
||||
providers: [ResponseSchemaService],
|
||||
providers: [
|
||||
ResponseSchemaService,
|
||||
AuthService,
|
||||
{
|
||||
provide: Logger,
|
||||
useValue: {
|
||||
info: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
getUserByUsername: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceMemberService,
|
||||
useValue: {
|
||||
findAllByUserId: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: AuthService,
|
||||
useValue: {
|
||||
create: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: Logger,
|
||||
useValue: {
|
||||
error: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
controller = module.get<ResponseSchemaController>(ResponseSchemaController);
|
||||
@ -66,5 +104,146 @@ describe('ResponseSchemaController', () => {
|
||||
new HttpException('问卷已删除', EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED),
|
||||
);
|
||||
});
|
||||
|
||||
it('whitelistValidate should throw SurveyNotFoundException when survey is removed', async () => {
|
||||
const surveyPath = '';
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValue(null);
|
||||
await expect(
|
||||
controller.whitelistValidate(surveyPath, {
|
||||
password: '123456',
|
||||
}),
|
||||
).rejects.toThrow(new SurveyNotFoundException('该问卷不存在,无法提交'));
|
||||
});
|
||||
|
||||
it('whitelistValidate should throw WHITELIST_ERROR code when password is incorrect', async () => {
|
||||
const surveyPath = '';
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValue({
|
||||
curStatus: {
|
||||
status: 'published',
|
||||
},
|
||||
code: {
|
||||
baseConf: {
|
||||
passwordSwitch: true,
|
||||
password: '123456',
|
||||
},
|
||||
},
|
||||
} as ResponseSchema);
|
||||
await expect(
|
||||
controller.whitelistValidate(surveyPath, {
|
||||
password: '123457',
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||
);
|
||||
});
|
||||
|
||||
it('whitelistValidate should be successfully', async () => {
|
||||
const surveyPath = 'test';
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValue({
|
||||
curStatus: {
|
||||
status: 'published',
|
||||
},
|
||||
code: {
|
||||
baseConf: {
|
||||
passwordSwitch: true,
|
||||
password: '123456',
|
||||
},
|
||||
},
|
||||
} as ResponseSchema);
|
||||
|
||||
await expect(
|
||||
controller.whitelistValidate(surveyPath, {
|
||||
password: '123456',
|
||||
}),
|
||||
).resolves.toEqual({ code: 200, data: null });
|
||||
});
|
||||
|
||||
it('whitelistValidate should throw WHITELIST_ERROR code when mobile or email is incorrect', async () => {
|
||||
const surveyPath = '';
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValue({
|
||||
curStatus: {
|
||||
status: 'published',
|
||||
},
|
||||
code: {
|
||||
baseConf: {
|
||||
passwordSwitch: true,
|
||||
password: '123456',
|
||||
whitelistType: 'CUSTOM',
|
||||
memberType: 'MOBILE',
|
||||
whitelist: ['13500000000'],
|
||||
},
|
||||
},
|
||||
} as ResponseSchema);
|
||||
await expect(
|
||||
controller.whitelistValidate(surveyPath, {
|
||||
password: '123456',
|
||||
whitelist: '13500000001',
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||
);
|
||||
});
|
||||
|
||||
it('whitelistValidate should throw WHITELIST_ERROR code when member is incorrect', async () => {
|
||||
const surveyPath = '';
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValue({
|
||||
curStatus: {
|
||||
status: 'published',
|
||||
},
|
||||
code: {
|
||||
baseConf: {
|
||||
passwordSwitch: true,
|
||||
password: '123456',
|
||||
whitelistType: 'MEMBER',
|
||||
whitelist: ['Jack'],
|
||||
},
|
||||
},
|
||||
} as ResponseSchema);
|
||||
await expect(
|
||||
controller.whitelistValidate(surveyPath, {
|
||||
password: '123456',
|
||||
whitelist: 'James',
|
||||
}),
|
||||
).rejects.toThrow(
|
||||
new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it('whitelistValidate should return verifyId successfully', async () => {
|
||||
const surveyPath = '';
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValue({
|
||||
curStatus: {
|
||||
status: 'published',
|
||||
},
|
||||
code: {
|
||||
baseConf: {
|
||||
passwordSwitch: true,
|
||||
password: '123456',
|
||||
whitelistType: 'CUSTOM',
|
||||
memberType: 'MOBILE',
|
||||
whitelist: ['13500000000'],
|
||||
},
|
||||
},
|
||||
} as ResponseSchema);
|
||||
|
||||
await expect(
|
||||
controller.whitelistValidate(surveyPath, {
|
||||
password: '123456',
|
||||
whitelist: '13500000000',
|
||||
}),
|
||||
).resolves.toEqual({ code: 200, data: null });
|
||||
});
|
||||
});
|
||||
|
@ -21,6 +21,10 @@ import { ResponseSecurityPlugin } from 'src/securityPlugin/responseSecurityPlugi
|
||||
import { RECORD_STATUS } from 'src/enums';
|
||||
import { SurveyResponse } from 'src/models/surveyResponse.entity';
|
||||
import { Logger } from 'src/logger';
|
||||
import { ResponseSchema } from 'src/models/responseSchema.entity';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { UserService } from 'src/modules/auth/services/user.service';
|
||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||
|
||||
const mockDecryptErrorBody = {
|
||||
surveyPath: 'EBzdmnSp',
|
||||
@ -124,6 +128,18 @@ describe('SurveyResponseController', () => {
|
||||
info: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: UserService,
|
||||
useValue: {
|
||||
getUserByUsername: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
provide: WorkspaceMemberService,
|
||||
useValue: {
|
||||
findAllByUserId: jest.fn(),
|
||||
},
|
||||
},
|
||||
],
|
||||
}).compile();
|
||||
|
||||
@ -306,5 +322,31 @@ describe('SurveyResponseController', () => {
|
||||
HttpException,
|
||||
);
|
||||
});
|
||||
|
||||
it('should throw HttpException if password does not match', async () => {
|
||||
const reqBody = {
|
||||
...mockSubmitData,
|
||||
password: '123457',
|
||||
sign: '4ff02062141d92d80629eae4797ba68056f29a9709cdf59bf206776fc0971c1a.1710400229589',
|
||||
};
|
||||
|
||||
jest
|
||||
.spyOn(responseSchemaService, 'getResponseSchemaByPath')
|
||||
.mockResolvedValueOnce({
|
||||
curStatus: {
|
||||
status: RECORD_STATUS.PUBLISHED,
|
||||
},
|
||||
code: {
|
||||
baseConf: {
|
||||
passwordSwitch: true,
|
||||
password: '123456',
|
||||
},
|
||||
},
|
||||
} as ResponseSchema);
|
||||
|
||||
await expect(controller.createResponse(reqBody, {})).rejects.toThrow(
|
||||
new HttpException('白名单验证失败', EXCEPTION_CODE.WHITELIST_ERROR),
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,14 +1,33 @@
|
||||
import { Controller, Get, HttpCode, Query } from '@nestjs/common';
|
||||
import {
|
||||
Body,
|
||||
Controller,
|
||||
Get,
|
||||
HttpCode,
|
||||
Param,
|
||||
Post,
|
||||
Query,
|
||||
} from '@nestjs/common';
|
||||
import { ResponseSchemaService } from '../services/responseScheme.service';
|
||||
import { HttpException } from 'src/exceptions/httpException';
|
||||
import { EXCEPTION_CODE } from 'src/enums/exceptionCode';
|
||||
import { RECORD_STATUS } from 'src/enums';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import Joi from 'joi';
|
||||
import { Logger } from 'src/logger';
|
||||
import { SurveyNotFoundException } from 'src/exceptions/surveyNotFoundException';
|
||||
import { WhitelistType } from 'src/interfaces/survey';
|
||||
import { UserService } from 'src/modules/auth/services/user.service';
|
||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||
|
||||
@ApiTags('surveyResponse')
|
||||
@Controller('/api/responseSchema')
|
||||
export class ResponseSchemaController {
|
||||
constructor(private readonly responseSchemaService: ResponseSchemaService) {}
|
||||
constructor(
|
||||
private readonly responseSchemaService: ResponseSchemaService,
|
||||
private readonly logger: Logger,
|
||||
private readonly userService: UserService,
|
||||
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||
) {}
|
||||
|
||||
@Get('/getSchema')
|
||||
@HttpCode(200)
|
||||
@ -34,9 +53,79 @@ export class ResponseSchemaController {
|
||||
EXCEPTION_CODE.RESPONSE_SCHEMA_REMOVED,
|
||||
);
|
||||
}
|
||||
|
||||
// 去掉C端的敏感字段
|
||||
if (responseSchema.code?.baseConf) {
|
||||
responseSchema.code.baseConf.password = null;
|
||||
responseSchema.code.baseConf.whitelist = [];
|
||||
}
|
||||
return {
|
||||
code: 200,
|
||||
data: responseSchema,
|
||||
};
|
||||
}
|
||||
|
||||
// 白名单验证
|
||||
@Post('/:surveyPath/validate')
|
||||
@HttpCode(200)
|
||||
async whitelistValidate(@Param('surveyPath') surveyPath, @Body() body) {
|
||||
const { value, error } = Joi.object({
|
||||
password: Joi.string().allow(null, ''),
|
||||
whitelist: Joi.string().allow(null, ''),
|
||||
}).validate(body, { allowUnknown: true });
|
||||
|
||||
if (error) {
|
||||
this.logger.error(`whitelistValidate error: ${error.message}`, {});
|
||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
// 问卷信息
|
||||
const schema =
|
||||
await this.responseSchemaService.getResponseSchemaByPath(surveyPath);
|
||||
if (!schema || schema.curStatus.status === 'removed') {
|
||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||
}
|
||||
|
||||
const { password, whitelist: whitelistValue } = value;
|
||||
const {
|
||||
passwordSwitch,
|
||||
password: settingPassword,
|
||||
whitelistType,
|
||||
whitelist,
|
||||
} = schema.code.baseConf;
|
||||
|
||||
// 密码校验
|
||||
if (passwordSwitch) {
|
||||
if (settingPassword !== password) {
|
||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// 名单校验(手机号/邮箱)
|
||||
if (whitelistType === WhitelistType.CUSTOM) {
|
||||
if (!whitelist.includes(whitelistValue)) {
|
||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
// 团队成员昵称校验
|
||||
if (whitelistType === WhitelistType.MEMBER) {
|
||||
const user = await this.userService.getUserByUsername(whitelistValue);
|
||||
if (!user) {
|
||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||
}
|
||||
|
||||
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
||||
{ userId: user._id.toString() },
|
||||
);
|
||||
if (!workspaceMember.length) {
|
||||
throw new HttpException('验证失败', EXCEPTION_CODE.WHITELIST_ERROR);
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: null,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -17,6 +17,9 @@ import * as Joi from 'joi';
|
||||
import * as forge from 'node-forge';
|
||||
import { ApiTags } from '@nestjs/swagger';
|
||||
import { Logger } from 'src/logger';
|
||||
import { WhitelistType } from 'src/interfaces/survey';
|
||||
import { UserService } from 'src/modules/auth/services/user.service';
|
||||
import { WorkspaceMemberService } from 'src/modules/workspace/services/workspaceMember.service';
|
||||
|
||||
@ApiTags('surveyResponse')
|
||||
@Controller('/api/surveyResponse')
|
||||
@ -28,6 +31,8 @@ export class SurveyResponseController {
|
||||
private readonly clientEncryptService: ClientEncryptService,
|
||||
private readonly messagePushingTaskService: MessagePushingTaskService,
|
||||
private readonly logger: Logger,
|
||||
private readonly userService: UserService,
|
||||
private readonly workspaceMemberService: WorkspaceMemberService,
|
||||
) {}
|
||||
|
||||
@Post('/createResponse')
|
||||
@ -43,6 +48,8 @@ export class SurveyResponseController {
|
||||
sessionId: Joi.string(),
|
||||
clientTime: Joi.number().required(),
|
||||
difTime: Joi.number(),
|
||||
password: Joi.string().allow(null, ''),
|
||||
whitelist: Joi.string().allow(null, ''),
|
||||
}).validate(reqBody, { allowUnknown: true });
|
||||
|
||||
if (error) {
|
||||
@ -52,8 +59,16 @@ export class SurveyResponseController {
|
||||
throw new HttpException('参数错误', EXCEPTION_CODE.PARAMETER_ERROR);
|
||||
}
|
||||
|
||||
const { surveyPath, encryptType, data, sessionId, clientTime, difTime } =
|
||||
value;
|
||||
const {
|
||||
surveyPath,
|
||||
encryptType,
|
||||
data,
|
||||
sessionId,
|
||||
clientTime,
|
||||
difTime,
|
||||
password,
|
||||
whitelist: whitelistValue,
|
||||
} = value;
|
||||
|
||||
// 查询schema
|
||||
const responseSchema =
|
||||
@ -62,6 +77,50 @@ export class SurveyResponseController {
|
||||
throw new SurveyNotFoundException('该问卷不存在,无法提交');
|
||||
}
|
||||
|
||||
// 白名单的verifyId校验
|
||||
const baseConf = responseSchema.code.baseConf;
|
||||
|
||||
// 密码校验
|
||||
if (baseConf?.passwordSwitch && baseConf.password) {
|
||||
if (baseConf.password !== password) {
|
||||
throw new HttpException(
|
||||
'白名单验证失败',
|
||||
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 名单校验(手机号/邮箱)
|
||||
if (baseConf?.whitelistType === WhitelistType.CUSTOM) {
|
||||
if (!baseConf.whitelist.includes(whitelistValue)) {
|
||||
throw new HttpException(
|
||||
'白名单验证失败',
|
||||
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// 团队成员昵称校验
|
||||
if (baseConf?.whitelistType === WhitelistType.MEMBER) {
|
||||
const user = await this.userService.getUserByUsername(whitelistValue);
|
||||
if (!user) {
|
||||
throw new HttpException(
|
||||
'白名单验证失败',
|
||||
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||
);
|
||||
}
|
||||
|
||||
const workspaceMember = await this.workspaceMemberService.findAllByUserId(
|
||||
{ userId: user._id.toString() },
|
||||
);
|
||||
if (!workspaceMember.length) {
|
||||
throw new HttpException(
|
||||
'白名单验证失败',
|
||||
EXCEPTION_CODE.WHITELIST_ERROR,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const now = Date.now();
|
||||
// 提交时间限制
|
||||
const begTime = responseSchema.code?.baseConf?.begTime || 0;
|
||||
|
@ -20,6 +20,8 @@ import { CounterController } from './controllers/counter.controller';
|
||||
import { ResponseSchemaController } from './controllers/responseSchema.controller';
|
||||
import { SurveyResponseController } from './controllers/surveyResponse.controller';
|
||||
import { SurveyResponseUIController } from './controllers/surveyResponseUI.controller';
|
||||
import { AuthModule } from '../auth/auth.module';
|
||||
import { WorkspaceModule } from '../workspace/workspace.module';
|
||||
|
||||
@Module({
|
||||
imports: [
|
||||
@ -31,6 +33,8 @@ import { SurveyResponseUIController } from './controllers/surveyResponseUI.contr
|
||||
]),
|
||||
ConfigModule,
|
||||
MessageModule,
|
||||
AuthModule,
|
||||
WorkspaceModule,
|
||||
],
|
||||
controllers: [
|
||||
ClientEncryptController,
|
||||
|
@ -35,6 +35,7 @@ describe('WorkspaceController', () => {
|
||||
findAllByIdWithPagination: jest.fn(),
|
||||
update: jest.fn(),
|
||||
delete: jest.fn(),
|
||||
findAllByUserId: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -46,6 +47,7 @@ describe('WorkspaceController', () => {
|
||||
batchUpdate: jest.fn(),
|
||||
batchDelete: jest.fn(),
|
||||
countByWorkspaceId: jest.fn(),
|
||||
batchSearchByWorkspace: jest.fn(),
|
||||
},
|
||||
},
|
||||
{
|
||||
@ -237,4 +239,31 @@ describe('WorkspaceController', () => {
|
||||
expect(workspaceService.delete).toHaveBeenCalledWith(id);
|
||||
});
|
||||
});
|
||||
|
||||
describe('getWorkspaceAndMember', () => {
|
||||
it('should return a list of workspaces and members for the user', async () => {
|
||||
const req = { user: { _id: new ObjectId() } };
|
||||
|
||||
const workspaceId = new ObjectId();
|
||||
const memberList = [{ workspaceId, userId: new ObjectId() }];
|
||||
const workspaces = [{ _id: workspaceId, name: 'Test Workspace' }];
|
||||
|
||||
jest
|
||||
.spyOn(workspaceService, 'findAllByUserId')
|
||||
.mockResolvedValue(workspaces as Array<Workspace>);
|
||||
jest
|
||||
.spyOn(workspaceMemberService, 'batchSearchByWorkspace')
|
||||
.mockResolvedValue(memberList as unknown as Array<WorkspaceMember>);
|
||||
|
||||
const result = await controller.getWorkspaceAndMember(req);
|
||||
|
||||
expect(result.code).toEqual(200);
|
||||
expect(workspaceService.findAllByUserId).toHaveBeenCalledWith(
|
||||
req.user._id.toString(),
|
||||
);
|
||||
expect(
|
||||
workspaceMemberService.batchSearchByWorkspace,
|
||||
).toHaveBeenCalledWith(workspaces.map((item) => item._id.toString()));
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -123,4 +123,25 @@ describe('WorkspaceService', () => {
|
||||
expect(surveyMetaRepository.updateMany).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
|
||||
describe('findAllByUserId', () => {
|
||||
it('should return all workspaces under a user', 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.findAllByUserId('');
|
||||
expect(result).toEqual(workspaces);
|
||||
expect(workspaceRepository.find).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -193,4 +193,21 @@ describe('WorkspaceMemberService', () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('batchSearchByWorkspace', () => {
|
||||
it('should return all workspace members by workspace id list', async () => {
|
||||
const workspaceList = ['workspaceId1', 'workspaceId2'];
|
||||
const members = [
|
||||
{ userId: 'userId1', workspaceId: workspaceList[0] },
|
||||
{ userId: 'userId2', workspaceId: workspaceList[1] },
|
||||
];
|
||||
|
||||
jest.spyOn(repository, 'find').mockResolvedValue(members as any);
|
||||
|
||||
const result = await service.batchSearchByWorkspace(workspaceList);
|
||||
|
||||
expect(result).toEqual(members);
|
||||
expect(repository.find).toHaveBeenCalledTimes(1);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -33,6 +33,8 @@ import { UserService } from 'src/modules/auth/services/user.service';
|
||||
import { SurveyMetaService } from 'src/modules/survey/services/surveyMeta.service';
|
||||
import { Logger } from 'src/logger';
|
||||
import { GetWorkspaceListDto } from '../dto/getWorkspaceList.dto';
|
||||
import { WorkspaceMember } from 'src/models/workspaceMember.entity';
|
||||
import { Workspace } from 'src/models/workspace.entity';
|
||||
|
||||
@ApiTags('workspace')
|
||||
@ApiBearerAuth()
|
||||
@ -349,4 +351,42 @@ export class WorkspaceController {
|
||||
code: 200,
|
||||
};
|
||||
}
|
||||
|
||||
@Get('/member/list')
|
||||
@HttpCode(200)
|
||||
async getWorkspaceAndMember(@Request() req) {
|
||||
const userId = req.user._id.toString();
|
||||
|
||||
// 所在所有空间
|
||||
const workspaceList = await this.workspaceService.findAllByUserId(userId);
|
||||
if (!workspaceList.length) {
|
||||
return {
|
||||
code: 200,
|
||||
data: [],
|
||||
};
|
||||
}
|
||||
|
||||
// 所有空间下的所有成员
|
||||
const workspaceMemberList =
|
||||
await this.workspaceMemberService.batchSearchByWorkspace(
|
||||
workspaceList.map((item) => item._id.toString()),
|
||||
);
|
||||
|
||||
const temp: Record<string, WorkspaceMember[]> = {};
|
||||
const list = workspaceList.map(
|
||||
(item: Workspace & { members: WorkspaceMember[] }) => {
|
||||
temp[item._id.toString()] = item.members = [];
|
||||
return item;
|
||||
},
|
||||
);
|
||||
|
||||
workspaceMemberList.forEach((member) => {
|
||||
temp[member.workspaceId.toString()].push(member);
|
||||
});
|
||||
|
||||
return {
|
||||
code: 200,
|
||||
data: list,
|
||||
};
|
||||
}
|
||||
}
|
||||
|
@ -149,4 +149,27 @@ export class WorkspaceService {
|
||||
surveyRes,
|
||||
};
|
||||
}
|
||||
|
||||
// 用户下的所有空间
|
||||
async findAllByUserId(userId: string) {
|
||||
return await this.workspaceRepository.find({
|
||||
where: {
|
||||
ownerId: userId,
|
||||
'curStatus.status': {
|
||||
$ne: RECORD_STATUS.REMOVED,
|
||||
},
|
||||
},
|
||||
order: {
|
||||
_id: -1,
|
||||
},
|
||||
select: [
|
||||
'_id',
|
||||
'curStatus',
|
||||
'name',
|
||||
'description',
|
||||
'ownerId',
|
||||
'createDate',
|
||||
],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
@ -140,4 +140,19 @@ export class WorkspaceMemberService {
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
// 根据空间id批量查询成员
|
||||
async batchSearchByWorkspace(workspaceList: string[]) {
|
||||
return await this.workspaceMemberRepository.find({
|
||||
where: {
|
||||
workspaceId: {
|
||||
$in: workspaceList,
|
||||
},
|
||||
},
|
||||
order: {
|
||||
_id: -1,
|
||||
},
|
||||
select: ['_id', 'userId', 'username', 'role', 'workspaceId'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user