From eb516dca8d9014b4218e5752a04d084b1d8cf8be Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 4 Oct 2024 16:38:13 +0900 Subject: [PATCH 01/28] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=EA=B0=80=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=84=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B0=BE=EA=B8=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/users/users.service.ts | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/users/users.service.ts b/nestjs-BE/server/src/users/users.service.ts index 72f28a0a..e68104eb 100644 --- a/nestjs-BE/server/src/users/users.service.ts +++ b/nestjs-BE/server/src/users/users.service.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateUserPrismaDto } from './dto/create-user.dto'; -import { User } from '@prisma/client'; +import { Space, User } from '@prisma/client'; import { v4 as uuid } from 'uuid'; @Injectable() @@ -35,4 +35,12 @@ export class UsersService { return user; }); } + + async findUserJoinedSpaces(userUuid: string): Promise<Space[]> { + const spaces = await this.prisma.space.findMany({ + where: { profileSpaces: { some: { profile: { userUuid } } } }, + }); + + return spaces; + } } From 43f685df8419e709ea8ca611694b255547f80a8f Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 4 Oct 2024 16:52:20 +0900 Subject: [PATCH 02/28] =?UTF-8?q?test:=20UsersController=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/users/users.controller.spec.ts | 42 +++++++++++++++++++ .../server/src/users/users.controller.ts | 4 ++ nestjs-BE/server/src/users/users.module.ts | 2 + 3 files changed, 48 insertions(+) create mode 100644 nestjs-BE/server/src/users/users.controller.spec.ts create mode 100644 nestjs-BE/server/src/users/users.controller.ts diff --git a/nestjs-BE/server/src/users/users.controller.spec.ts b/nestjs-BE/server/src/users/users.controller.spec.ts new file mode 100644 index 00000000..ea8344b5 --- /dev/null +++ b/nestjs-BE/server/src/users/users.controller.spec.ts @@ -0,0 +1,42 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { UsersController } from './users.controller'; +import { UsersService } from './users.service'; +import { HttpStatus } from '@nestjs/common'; +import { RequestWithUser } from '../utils/interface'; + +describe('UsersController', () => { + let controller: UsersController; + let usersService: UsersService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + controllers: [UsersController], + providers: [ + { + provide: UsersService, + useValue: { findUserJoinedSpaces: jest.fn() }, + }, + ], + }).compile(); + + controller = module.get<UsersController>(UsersController); + usersService = module.get<UsersService>(UsersService); + }); + + it('findUserJoinedSpaces', async () => { + const reqMock = { user: { uuid: 'user uuid' } } as RequestWithUser; + const spacesMock = []; + + (usersService.findUserJoinedSpaces as jest.Mock).mockResolvedValue( + spacesMock, + ); + + const response = controller.findUserJoinedSpaces(reqMock); + + await expect(response).resolves.toEqual({ + statusCode: HttpStatus.OK, + message: 'OK', + data: spacesMock, + }); + }); +}); diff --git a/nestjs-BE/server/src/users/users.controller.ts b/nestjs-BE/server/src/users/users.controller.ts new file mode 100644 index 00000000..43b3842a --- /dev/null +++ b/nestjs-BE/server/src/users/users.controller.ts @@ -0,0 +1,4 @@ +import { Controller } from '@nestjs/common'; + +@Controller('users') +export class UsersController {} diff --git a/nestjs-BE/server/src/users/users.module.ts b/nestjs-BE/server/src/users/users.module.ts index 8fa904f1..276e9b63 100644 --- a/nestjs-BE/server/src/users/users.module.ts +++ b/nestjs-BE/server/src/users/users.module.ts @@ -1,7 +1,9 @@ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; +import { UsersController } from './users.controller'; @Module({ + controllers: [UsersController], providers: [UsersService], exports: [UsersService], }) From e63ef8a649b62e0d044b23c641355477748e60ad Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 4 Oct 2024 17:06:46 +0900 Subject: [PATCH 03/28] =?UTF-8?q?fix:=20PrismaModule=20=EC=9D=98=EC=A1=B4?= =?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/users/users.module.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/nestjs-BE/server/src/users/users.module.ts b/nestjs-BE/server/src/users/users.module.ts index 276e9b63..acfee4ee 100644 --- a/nestjs-BE/server/src/users/users.module.ts +++ b/nestjs-BE/server/src/users/users.module.ts @@ -1,8 +1,10 @@ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; +import { PrismaModule } from '../prisma/prisma.module'; @Module({ + imports: [PrismaModule], controllers: [UsersController], providers: [UsersService], exports: [UsersService], From e6c970193de6e3c408d6ff41acdff0b57cce9764 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 4 Oct 2024 18:25:12 +0900 Subject: [PATCH 04/28] =?UTF-8?q?test:=20e2e=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/users.e2e-spec.ts | 102 ++++++++++++++++++++++++ 1 file changed, 102 insertions(+) create mode 100644 nestjs-BE/server/test/users.e2e-spec.ts diff --git a/nestjs-BE/server/test/users.e2e-spec.ts b/nestjs-BE/server/test/users.e2e-spec.ts new file mode 100644 index 00000000..128d2e61 --- /dev/null +++ b/nestjs-BE/server/test/users.e2e-spec.ts @@ -0,0 +1,102 @@ +import { HttpStatus, INestApplication } from '@nestjs/common'; +import { Test, TestingModule } from '@nestjs/testing'; +import { ConfigModule, ConfigService } from '@nestjs/config'; +import { User } from '@prisma/client'; +import * as request from 'supertest'; +import { v4 as uuid } from 'uuid'; +import { sign } from 'jsonwebtoken'; +import { PrismaService } from '../src/prisma/prisma.service'; +import { UsersModule } from '../src/users/users.module'; +import { AuthModule } from '../src/auth/auth.module'; + +describe('UsersController (e2e)', () => { + let app: INestApplication; + let prisma: PrismaService; + let configService: ConfigService; + let testToken: string; + let testUser: User; + + beforeAll(async () => { + const module: TestingModule = await Test.createTestingModule({ + imports: [ + ConfigModule.forRoot({ isGlobal: true }), + UsersModule, + AuthModule, + ], + }).compile(); + + app = module.createNestApplication(); + + await app.init(); + + prisma = module.get<PrismaService>(PrismaService); + configService = module.get<ConfigService>(ConfigService); + + await prisma.user.deleteMany({}); + + testUser = await prisma.user.create({ data: { uuid: uuid() } }); + testToken = sign( + { sub: testUser.uuid }, + configService.get<string>('JWT_ACCESS_SECRET'), + { expiresIn: '5m' }, + ); + }); + + beforeEach(async () => { + await prisma.profile.deleteMany({}); + await prisma.space.deleteMany({}); + await prisma.profileSpace.deleteMany({}); + }); + + afterAll(async () => { + await app.close(); + }); + + it('users/spaces (GET)', async () => { + const SPACE_NUMBER = 5; + + const profile = await prisma.profile.create({ + data: { + uuid: uuid(), + userUuid: testUser.uuid, + image: 'test image', + nickname: 'test nickname', + }, + }); + const spaces = []; + for (let j = 0; j < SPACE_NUMBER; j++) { + const space = await prisma.space.create({ + data: { uuid: uuid(), name: 'test space', icon: 'test icon' }, + }); + await prisma.profileSpace.create({ + data: { profileUuid: profile.uuid, spaceUuid: space.uuid }, + }); + spaces.push(space); + } + + return request(app.getHttpServer()) + .get('/users/spaces') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect((res) => { + expect(res.body.statusCode).toBe(HttpStatus.OK); + expect(res.body.message).toBe('OK'); + expect(res.body.data).toEqual(expect.arrayContaining(spaces)); + }); + }); + + it('users/spaces (GET) no joined spaces', async () => { + return request(app.getHttpServer()) + .get('/users/spaces') + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect({ statusCode: HttpStatus.OK, message: 'OK', data: [] }); + }); + + it('users/spaces (GET)', async () => { + return request(app.getHttpServer()) + .get('/users/spaces') + .expect(HttpStatus.UNAUTHORIZED) + .expect({ statusCode: HttpStatus.UNAUTHORIZED, message: 'Unauthorized' }); + }); +}); From 8649827e44b6d1a75cdcede671099dff5e625da8 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 4 Oct 2024 18:29:00 +0900 Subject: [PATCH 05/28] =?UTF-8?q?feat:=20=EC=9C=A0=EC=A0=80=EA=B0=80=20?= =?UTF-8?q?=EB=93=A4=EC=96=B4=EA=B0=84=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=AA=A9=EB=A1=9D=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/users/users.controller.ts | 26 +++++++++++++++++-- 1 file changed, 24 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/server/src/users/users.controller.ts b/nestjs-BE/server/src/users/users.controller.ts index 43b3842a..a2256192 100644 --- a/nestjs-BE/server/src/users/users.controller.ts +++ b/nestjs-BE/server/src/users/users.controller.ts @@ -1,4 +1,26 @@ -import { Controller } from '@nestjs/common'; +import { Controller, Get, HttpStatus, Req } from '@nestjs/common'; +import { ApiOperation, ApiResponse, ApiTags } from '@nestjs/swagger'; +import { UsersService } from './users.service'; +import { RequestWithUser } from '../utils/interface'; @Controller('users') -export class UsersController {} +@ApiTags('users') +export class UsersController { + constructor(private readonly usersService: UsersService) {} + + @Get('spaces') + @ApiOperation({ summary: 'Get spaces user joined.' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Spaces found.', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: 'User not logged in.', + }) + async findUserJoinedSpaces(@Req() req: RequestWithUser) { + const spaces = await this.usersService.findUserJoinedSpaces(req.user.uuid); + + return { statusCode: HttpStatus.OK, message: 'OK', data: spaces }; + } +} From 29beac15ffacd1b703b0274e56da0830a2f5187a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Fri, 4 Oct 2024 18:37:23 +0900 Subject: [PATCH 06/28] =?UTF-8?q?refactor:=20=EB=B9=84=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/users.e2e-spec.ts | 27 ++++++++++++++++--------- 1 file changed, 17 insertions(+), 10 deletions(-) diff --git a/nestjs-BE/server/test/users.e2e-spec.ts b/nestjs-BE/server/test/users.e2e-spec.ts index 128d2e61..1f55a7a5 100644 --- a/nestjs-BE/server/test/users.e2e-spec.ts +++ b/nestjs-BE/server/test/users.e2e-spec.ts @@ -63,16 +63,23 @@ describe('UsersController (e2e)', () => { nickname: 'test nickname', }, }); - const spaces = []; - for (let j = 0; j < SPACE_NUMBER; j++) { - const space = await prisma.space.create({ - data: { uuid: uuid(), name: 'test space', icon: 'test icon' }, - }); - await prisma.profileSpace.create({ - data: { profileUuid: profile.uuid, spaceUuid: space.uuid }, - }); - spaces.push(space); - } + const spacePromises = Array.from({ length: SPACE_NUMBER }, async () => { + return prisma.space + .create({ + data: { uuid: uuid(), name: 'test space', icon: 'test icon' }, + }) + .then(async (space) => { + return prisma.profileSpace + .create({ + data: { + profileUuid: profile.uuid, + spaceUuid: space.uuid, + }, + }) + .then(() => space); + }); + }); + const spaces = await Promise.all(spacePromises); return request(app.getHttpServer()) .get('/users/spaces') From aeb19dbe4916894e3cb8559e6604a4fa8998f697 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sat, 5 Oct 2024 16:54:44 +0900 Subject: [PATCH 07/28] =?UTF-8?q?feat:=20=EC=9D=B4=EC=A0=84=20=EB=B2=84?= =?UTF-8?q?=EC=A0=84=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/spaces/dto/create-space.dto.ts | 16 +-- .../server/src/spaces/dto/update-space.dto.ts | 21 +--- .../src/spaces/spaces.controller.spec.ts | 34 +++--- .../server/src/spaces/spaces.controller.ts | 105 +----------------- nestjs-BE/server/src/spaces/spaces.module.ts | 4 +- nestjs-BE/server/test/spaces.e2e-spec.ts | 88 +++++++-------- 6 files changed, 71 insertions(+), 197 deletions(-) diff --git a/nestjs-BE/server/src/spaces/dto/create-space.dto.ts b/nestjs-BE/server/src/spaces/dto/create-space.dto.ts index 4e3414a0..11ba7b08 100644 --- a/nestjs-BE/server/src/spaces/dto/create-space.dto.ts +++ b/nestjs-BE/server/src/spaces/dto/create-space.dto.ts @@ -4,7 +4,7 @@ import { MAX_NAME_LENGTH } from '../../config/magic-number'; import { Expose } from 'class-transformer'; import { v4 as uuid } from 'uuid'; -export class CreateSpaceRequestV2Dto { +export class CreateSpaceRequestDto { @IsString() @IsNotEmpty() @MaxLength(MAX_NAME_LENGTH) @@ -24,20 +24,6 @@ export class CreateSpaceRequestV2Dto { icon: string; } -export class CreateSpaceRequestDto { - @IsString() - @IsNotEmpty() - @MaxLength(MAX_NAME_LENGTH) - @ApiProperty({ example: 'Sample Space', description: 'Name of the space' }) - name: string; - - @ApiProperty({ - example: 'space-icon.png', - description: 'Profile icon for the space', - }) - icon: string; -} - export class CreateSpacePrismaDto { name: string; icon: string; diff --git a/nestjs-BE/server/src/spaces/dto/update-space.dto.ts b/nestjs-BE/server/src/spaces/dto/update-space.dto.ts index 2af0b07e..56d141c5 100644 --- a/nestjs-BE/server/src/spaces/dto/update-space.dto.ts +++ b/nestjs-BE/server/src/spaces/dto/update-space.dto.ts @@ -2,7 +2,7 @@ import { ApiProperty } from '@nestjs/swagger'; import { IsNotEmpty, IsOptional, IsString, MaxLength } from 'class-validator'; import { MAX_NAME_LENGTH } from '../../config/magic-number'; -export class UpdateSpaceRequestV2Dto { +export class UpdateSpaceRequestDto { @IsOptional() @IsString() @IsNotEmpty() @@ -23,25 +23,6 @@ export class UpdateSpaceRequestV2Dto { icon: string; } -export class UpdateSpaceRequestDto { - @IsString() - @IsNotEmpty() - @MaxLength(MAX_NAME_LENGTH) - @ApiProperty({ - example: 'new space', - description: 'Updated space name', - required: false, - }) - name: string; - - @ApiProperty({ - example: 'new image', - description: 'Updated space icon', - required: false, - }) - icon: string; -} - export class UpdateSpacePrismaDto { name: string; icon: string; diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index fdc36092..bff5fa59 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -1,5 +1,5 @@ import { Test, TestingModule } from '@nestjs/testing'; -import { SpacesControllerV2 } from './spaces.controller'; +import { SpacesController } from './spaces.controller'; import { SpacesService } from './spaces.service'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; import { UploadService } from '../upload/upload.service'; @@ -11,13 +11,13 @@ import { HttpStatus, NotFoundException, } from '@nestjs/common'; -import { UpdateSpaceRequestV2Dto } from './dto/update-space.dto'; -import { CreateSpaceRequestV2Dto } from './dto/create-space.dto'; +import { UpdateSpaceRequestDto } from './dto/update-space.dto'; +import { CreateSpaceRequestDto } from './dto/create-space.dto'; import { RequestWithUser } from '../utils/interface'; import { ConfigModule, ConfigService } from '@nestjs/config'; -describe('SpacesControllerV2', () => { - let controller: SpacesControllerV2; +describe('SpacesController', () => { + let controller: SpacesController; let spacesService: SpacesService; let uploadService: UploadService; let profilesService: ProfilesService; @@ -27,7 +27,7 @@ describe('SpacesControllerV2', () => { beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ imports: [ConfigModule], - controllers: [SpacesControllerV2], + controllers: [SpacesController], providers: [ { provide: SpacesService, @@ -55,7 +55,7 @@ describe('SpacesControllerV2', () => { ], }).compile(); - controller = module.get<SpacesControllerV2>(SpacesControllerV2); + controller = module.get<SpacesController>(SpacesController); spacesService = module.get<SpacesService>(SpacesService); uploadService = module.get<UploadService>(UploadService); profilesService = module.get<ProfilesService>(ProfilesService); @@ -74,7 +74,7 @@ describe('SpacesControllerV2', () => { const bodyMock = { name: 'new space name', profileUuid: profileMock.uuid, - } as CreateSpaceRequestV2Dto; + } as CreateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; jest @@ -103,7 +103,7 @@ describe('SpacesControllerV2', () => { const bodyMock = { name: 'new space name', profileUuid: 'wrong profile uuid', - } as CreateSpaceRequestV2Dto; + } as CreateSpaceRequestDto; const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; jest @@ -127,7 +127,7 @@ describe('SpacesControllerV2', () => { const bodyMock = { name: 'new space name', profileUuid: profileMock.uuid, - } as CreateSpaceRequestV2Dto; + } as CreateSpaceRequestDto; jest .spyOn(profilesService, 'findProfileByProfileUuid') @@ -149,7 +149,7 @@ describe('SpacesControllerV2', () => { const bodyMock = { name: 'new space name', profileUuid: profileMock.uuid, - } as CreateSpaceRequestV2Dto; + } as CreateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; jest @@ -318,7 +318,7 @@ describe('SpacesControllerV2', () => { uuid: 'profile uuid', userUuid: requestMock.user.uuid, } as Profile; - const bodyMock = { name: 'new space name' } as UpdateSpaceRequestV2Dto; + const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; const profileSpaceMock = { spaceUuid: spaceMock.uuid, profileUuid: profileMock.uuid, @@ -354,7 +354,7 @@ describe('SpacesControllerV2', () => { }); it('update icon not requested', async () => { - const bodyMock = { name: 'new space name' } as UpdateSpaceRequestV2Dto; + const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; const profileMock = { @@ -396,7 +396,7 @@ describe('SpacesControllerV2', () => { it('update name not requested', async () => { const iconMock = { filename: 'icon' } as Express.Multer.File; const iconUrlMock = 'www.test.com/image'; - const bodyMock = {} as UpdateSpaceRequestV2Dto; + const bodyMock = {} as UpdateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; const profileMock = { @@ -444,7 +444,7 @@ describe('SpacesControllerV2', () => { uuid: 'profile uuid', userUuid: 'new user uuid', } as Profile; - const bodyMock = { name: 'new space name' } as UpdateSpaceRequestV2Dto; + const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; jest .spyOn(profilesService, 'findProfileByProfileUuid') @@ -471,7 +471,7 @@ describe('SpacesControllerV2', () => { uuid: 'profile uuid', userUuid: requestMock.user.uuid, } as Profile; - const bodyMock = { name: 'new space name' } as UpdateSpaceRequestV2Dto; + const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; jest .spyOn(profilesService, 'findProfileByProfileUuid') @@ -501,7 +501,7 @@ describe('SpacesControllerV2', () => { uuid: 'profile uuid', userUuid: requestMock.user.uuid, } as Profile; - const bodyMock = { name: 'new space name' } as UpdateSpaceRequestV2Dto; + const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; jest .spyOn(profilesService, 'findProfileByProfileUuid') diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index fbf7df6c..9ac17081 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -18,14 +18,8 @@ import { } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { SpacesService } from './spaces.service'; -import { - CreateSpaceRequestDto, - CreateSpaceRequestV2Dto, -} from './dto/create-space.dto'; -import { - UpdateSpaceRequestDto, - UpdateSpaceRequestV2Dto, -} from './dto/update-space.dto'; +import { CreateSpaceRequestDto } from './dto/create-space.dto'; +import { UpdateSpaceRequestDto } from './dto/update-space.dto'; import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { UploadService } from '../upload/upload.service'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; @@ -33,9 +27,9 @@ import { RequestWithUser } from '../utils/interface'; import { ProfilesService } from '../profiles/profiles.service'; import { ConfigService } from '@nestjs/config'; -@Controller('v2/spaces') +@Controller('spaces') @ApiTags('spaces') -export class SpacesControllerV2 { +export class SpacesController { constructor( private readonly spacesService: SpacesService, private readonly uploadService: UploadService, @@ -76,7 +70,7 @@ export class SpacesControllerV2 { disableErrorMessages: true, }), ) - createSpaceDto: CreateSpaceRequestV2Dto, + createSpaceDto: CreateSpaceRequestDto, @Req() req: RequestWithUser, ) { if (!createSpaceDto.profileUuid) throw new BadRequestException(); @@ -170,7 +164,7 @@ export class SpacesControllerV2 { @Param('space_uuid') spaceUuid: string, @Query('profile_uuid') profileUuid: string, @Body(new ValidationPipe({ whitelist: true, disableErrorMessages: true })) - updateSpaceDto: UpdateSpaceRequestV2Dto, + updateSpaceDto: UpdateSpaceRequestDto, @Req() req: RequestWithUser, ) { if (!profileUuid) throw new BadRequestException(); @@ -197,90 +191,3 @@ export class SpacesControllerV2 { return { statusCode: HttpStatus.OK, message: 'OK', data: space }; } } - -/* - OLD VERSION -*/ -@Controller('spaces') -@ApiTags('spaces') -export class SpacesController { - constructor( - private readonly spacesService: SpacesService, - private readonly uploadService: UploadService, - private readonly profileSpaceService: ProfileSpaceService, - private readonly profilesService: ProfilesService, - private readonly configService: ConfigService, - ) {} - - @Post() - @UseInterceptors(FileInterceptor('icon')) - @ApiOperation({ summary: 'Create space' }) - @ApiResponse({ - status: 201, - description: 'The space has been successfully created.', - }) - async create( - @UploadedFile() icon: Express.Multer.File, - @Body(new ValidationPipe({ whitelist: true, disableErrorMessages: true })) - createSpaceDto: CreateSpaceRequestDto, - @Req() req: RequestWithUser, - ) { - const profile = await this.profilesService.findProfile(req.user.uuid); - if (!profile) throw new NotFoundException(); - const iconUrl = icon - ? await this.uploadService.uploadFile(icon) - : this.configService.get<string>('APP_ICON_URL'); - createSpaceDto.icon = iconUrl; - const space = await this.spacesService.createSpace(createSpaceDto); - await this.profileSpaceService.joinSpace(profile.uuid, space.uuid); - return { statusCode: 201, message: 'Created', data: space }; - } - - @Get(':space_uuid') - @ApiOperation({ summary: 'Get space by space_uuid' }) - @ApiResponse({ - status: 200, - description: 'Return the space data.', - }) - @ApiResponse({ - status: 404, - description: 'Space not found.', - }) - async findOne(@Param('space_uuid') spaceUuid: string) { - const space = await this.spacesService.findSpace(spaceUuid); - if (!space) throw new NotFoundException(); - return { statusCode: 200, message: 'Success', data: space }; - } - - @Patch(':space_uuid') - @UseInterceptors(FileInterceptor('icon')) - @ApiOperation({ summary: 'Update space by space_uuid' }) - @ApiResponse({ - status: 200, - description: 'Space has been successfully updated.', - }) - @ApiResponse({ - status: 400, - description: 'Bad Request. Invalid input data.', - }) - @ApiResponse({ - status: 404, - description: 'Space not found.', - }) - async update( - @UploadedFile() icon: Express.Multer.File, - @Param('space_uuid') spaceUuid: string, - @Body(new ValidationPipe({ whitelist: true, disableErrorMessages: true })) - updateSpaceDto: UpdateSpaceRequestDto, - ) { - if (icon) { - updateSpaceDto.icon = await this.uploadService.uploadFile(icon); - } - const space = await this.spacesService.updateSpace( - spaceUuid, - updateSpaceDto, - ); - if (!space) throw new NotFoundException(); - return { statusCode: 200, message: 'Success', data: space }; - } -} diff --git a/nestjs-BE/server/src/spaces/spaces.module.ts b/nestjs-BE/server/src/spaces/spaces.module.ts index f3a866a7..52c1bf25 100644 --- a/nestjs-BE/server/src/spaces/spaces.module.ts +++ b/nestjs-BE/server/src/spaces/spaces.module.ts @@ -1,13 +1,13 @@ import { forwardRef, Module } from '@nestjs/common'; import { SpacesService } from './spaces.service'; -import { SpacesController, SpacesControllerV2 } from './spaces.controller'; +import { SpacesController } from './spaces.controller'; import { UploadModule } from '../upload/upload.module'; import { ProfileSpaceModule } from '../profile-space/profile-space.module'; import { ProfilesModule } from '../profiles/profiles.module'; @Module({ imports: [forwardRef(() => ProfileSpaceModule), ProfilesModule, UploadModule], - controllers: [SpacesController, SpacesControllerV2], + controllers: [SpacesController], providers: [SpacesService], exports: [SpacesService], }) diff --git a/nestjs-BE/server/test/spaces.e2e-spec.ts b/nestjs-BE/server/test/spaces.e2e-spec.ts index 9792462f..322b28b6 100644 --- a/nestjs-BE/server/test/spaces.e2e-spec.ts +++ b/nestjs-BE/server/test/spaces.e2e-spec.ts @@ -75,7 +75,7 @@ describe('SpacesController (e2e)', () => { await app.close(); }); - it('/v2/spaces (POST)', () => { + it('/spaces (POST)', () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -90,7 +90,7 @@ describe('SpacesController (e2e)', () => { const imageRegExp = new RegExp(imageUrlPattern); return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .field('profile_uuid', testProfile.uuid) @@ -107,11 +107,11 @@ describe('SpacesController (e2e)', () => { }); }); - it('/v2/spaces (POST) without space image', () => { + it('/spaces (POST) without space image', () => { const newSpace = { name: 'new test space' }; return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .auth(testToken, { type: 'bearer' }) .send({ name: newSpace.name, profile_uuid: testProfile.uuid }) .expect(HttpStatus.CREATED) @@ -128,7 +128,7 @@ describe('SpacesController (e2e)', () => { }); }); - it('/v2/spaces (POST) without profile uuid', () => { + it('/spaces (POST) without profile uuid', () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -136,7 +136,7 @@ describe('SpacesController (e2e)', () => { }; return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) @@ -144,14 +144,14 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Bad Request', statusCode: HttpStatus.BAD_REQUEST }); }); - it('/v2/spaces (POST) without space name', () => { + it('/spaces (POST) without space name', () => { const newSpace = { icon: testImagePath, iconContentType: 'image/png', }; return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .auth(testToken, { type: 'bearer' }) .field('profile_uuid', testProfile.uuid) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) @@ -159,14 +159,14 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Bad Request', statusCode: HttpStatus.BAD_REQUEST }); }); - it('/v2/spaces (POST) not logged in', () => { + it('/spaces (POST) not logged in', () => { return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .expect(HttpStatus.UNAUTHORIZED) .expect({ message: 'Unauthorized', statusCode: HttpStatus.UNAUTHORIZED }); }); - it("/v2/spaces (POST) profile user doesn't have", async () => { + it("/spaces (POST) profile user doesn't have", async () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -183,7 +183,7 @@ describe('SpacesController (e2e)', () => { }); return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .field('profile_uuid', newProfile.uuid) @@ -192,7 +192,7 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); }); - it('/v2/spaces (POST) profilie not found', () => { + it('/spaces (POST) profilie not found', () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -200,7 +200,7 @@ describe('SpacesController (e2e)', () => { }; return request(app.getHttpServer()) - .post('/v2/spaces') + .post('/spaces') .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .field('profile_uuid', uuid()) @@ -209,13 +209,13 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) space found', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) space found', async () => { await prisma.profileSpace.create({ data: { spaceUuid: testSpace.uuid, profileUuid: testProfile.uuid }, }); return request(app.getHttpServer()) - .get(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .get(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .expect(HttpStatus.OK) .expect({ @@ -225,26 +225,26 @@ describe('SpacesController (e2e)', () => { }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) query profile_uuid needed', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) query profile_uuid needed', async () => { return request(app.getHttpServer()) - .get(`/v2/spaces/${testSpace.uuid}`) + .get(`/spaces/${testSpace.uuid}`) .auth(testToken, { type: 'bearer' }) .expect(HttpStatus.BAD_REQUEST) .expect({ message: 'Bad Request', statusCode: HttpStatus.BAD_REQUEST }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) not logged in', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) not logged in', async () => { await prisma.profileSpace.create({ data: { spaceUuid: testSpace.uuid, profileUuid: testProfile.uuid }, }); return request(app.getHttpServer()) - .get(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .get(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .expect(HttpStatus.UNAUTHORIZED) .expect({ message: 'Unauthorized', statusCode: HttpStatus.UNAUTHORIZED }); }); - it("/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) profile user doesn't have", async () => { + it("/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) profile user doesn't have", async () => { const newUser = await prisma.user.create({ data: { uuid: uuid() } }); const newProfile = await prisma.profile.create({ data: { @@ -259,37 +259,37 @@ describe('SpacesController (e2e)', () => { }); return request(app.getHttpServer()) - .get(`/v2/spaces/${testSpace.uuid}?profile_uuid=${newProfile.uuid}`) + .get(`/spaces/${testSpace.uuid}?profile_uuid=${newProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .expect(HttpStatus.FORBIDDEN) .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) profile not existing', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) profile not existing', async () => { return request(app.getHttpServer()) - .get(`/v2/spaces/${testSpace.uuid}?profile_uuid=${uuid()}`) + .get(`/spaces/${testSpace.uuid}?profile_uuid=${uuid()}`) .auth(testToken, { type: 'bearer' }) .expect(HttpStatus.NOT_FOUND) .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) findOne profile not joined space', () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) findOne profile not joined space', () => { return request(app.getHttpServer()) - .get(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .get(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .expect(HttpStatus.FORBIDDEN) .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) not existing space', () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (GET) not existing space', () => { return request(app.getHttpServer()) - .get(`/v2/spaces/${uuid()}?profile_uuid=${testProfile.uuid}`) + .get(`/spaces/${uuid()}?profile_uuid=${testProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .expect(HttpStatus.NOT_FOUND) .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) update success', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) update success', async () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -306,7 +306,7 @@ describe('SpacesController (e2e)', () => { const imageRegExp = new RegExp(imageUrlPattern); return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) @@ -320,7 +320,7 @@ describe('SpacesController (e2e)', () => { }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) request without name', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) request without name', async () => { const newSpace = { icon: testImagePath, iconContentType: 'image/png', @@ -336,7 +336,7 @@ describe('SpacesController (e2e)', () => { }); return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) .expect(HttpStatus.OK) @@ -349,14 +349,14 @@ describe('SpacesController (e2e)', () => { }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) request without icon', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) request without icon', async () => { const newSpace = { name: 'new test space' }; await prisma.profileSpace.create({ data: { spaceUuid: testSpace.uuid, profileUuid: testProfile.uuid }, }); return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .send({ name: newSpace.name }) .expect(HttpStatus.OK) @@ -371,7 +371,7 @@ describe('SpacesController (e2e)', () => { }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile uuid needed', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile uuid needed', async () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -379,7 +379,7 @@ describe('SpacesController (e2e)', () => { }; return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}`) + .patch(`/spaces/${testSpace.uuid}`) .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) @@ -387,12 +387,12 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Bad Request', statusCode: HttpStatus.BAD_REQUEST }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) unauthorized', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) unauthorized', async () => { const icon = await readFile(resolve(__dirname, './base_image.png')); const newSpace = { name: 'new test space', icon }; return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${testProfile.uuid}`) .field('name', newSpace.name) .attach('icon', newSpace.icon) .expect(HttpStatus.UNAUTHORIZED) @@ -402,7 +402,7 @@ describe('SpacesController (e2e)', () => { }); }); - it("/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile user doesn't have", async () => { + it("/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile user doesn't have", async () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -422,7 +422,7 @@ describe('SpacesController (e2e)', () => { }); return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${newProfile.uuid}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${newProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) @@ -430,7 +430,7 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile not joined space', async () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile not joined space', async () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -447,7 +447,7 @@ describe('SpacesController (e2e)', () => { }); return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${newProfile.uuid}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${newProfile.uuid}`) .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) @@ -455,7 +455,7 @@ describe('SpacesController (e2e)', () => { .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); }); - it('/v2/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile not found', () => { + it('/spaces/:space_uuid?profile_uuid={profile_uuid} (PATCH) profile not found', () => { const newSpace = { name: 'new test space', icon: testImagePath, @@ -463,7 +463,7 @@ describe('SpacesController (e2e)', () => { }; return request(app.getHttpServer()) - .patch(`/v2/spaces/${testSpace.uuid}?profile_uuid=${uuid()}`) + .patch(`/spaces/${testSpace.uuid}?profile_uuid=${uuid()}`) .auth(testToken, { type: 'bearer' }) .field('name', newSpace.name) .attach('icon', newSpace.icon, { contentType: newSpace.iconContentType }) From 7f5fcc061a4a801730bc1fe9f3e9c8e2b6c5b501 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 15:45:21 +0900 Subject: [PATCH 08/28] =?UTF-8?q?feat:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=86=8C=EC=9C=A0=20=EA=B2=80=EC=A6=9D=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/users/users.module.ts | 3 +- .../server/src/users/users.service.spec.ts | 47 +++++++++++++++++++ nestjs-BE/server/src/users/users.service.ts | 23 ++++++++- 3 files changed, 70 insertions(+), 3 deletions(-) diff --git a/nestjs-BE/server/src/users/users.module.ts b/nestjs-BE/server/src/users/users.module.ts index acfee4ee..6fd5b2ad 100644 --- a/nestjs-BE/server/src/users/users.module.ts +++ b/nestjs-BE/server/src/users/users.module.ts @@ -2,9 +2,10 @@ import { Module } from '@nestjs/common'; import { UsersService } from './users.service'; import { UsersController } from './users.controller'; import { PrismaModule } from '../prisma/prisma.module'; +import { ProfilesModule } from '../profiles/profiles.module'; @Module({ - imports: [PrismaModule], + imports: [PrismaModule, ProfilesModule], controllers: [UsersController], providers: [UsersService], exports: [UsersService], diff --git a/nestjs-BE/server/src/users/users.service.spec.ts b/nestjs-BE/server/src/users/users.service.spec.ts index 81f58428..c98fbd32 100644 --- a/nestjs-BE/server/src/users/users.service.spec.ts +++ b/nestjs-BE/server/src/users/users.service.spec.ts @@ -3,15 +3,22 @@ import { UsersService } from './users.service'; import { PrismaService } from '../prisma/prisma.service'; import { v4 as uuid } from 'uuid'; import { KakaoUser, User } from '@prisma/client'; +import { ProfilesService } from '../profiles/profiles.service'; +import { ForbiddenException, NotFoundException } from '@nestjs/common'; describe('UsersService', () => { let usersService: UsersService; + let profilesService: ProfilesService; let prisma: PrismaService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ providers: [ UsersService, + { + provide: ProfilesService, + useValue: { findProfileByProfileUuid: jest.fn() }, + }, { provide: PrismaService, useValue: { @@ -24,6 +31,7 @@ describe('UsersService', () => { }).compile(); usersService = module.get<UsersService>(UsersService); + profilesService = module.get<ProfilesService>(ProfilesService); prisma = module.get<PrismaService>(PrismaService); }); @@ -69,4 +77,43 @@ describe('UsersService', () => { expect(prisma.user.create).toHaveBeenCalled(); expect(prisma.user.findUnique).not.toHaveBeenCalled(); }); + + it('verifyUserProfile verified', async () => { + const userMock = { uuid: 'user uuid' }; + const profileMock = { uuid: 'profile uuid', userUuid: userMock.uuid }; + + (profilesService.findProfileByProfileUuid as jest.Mock).mockResolvedValue( + profileMock, + ); + + const res = usersService.verifyUserProfile(userMock.uuid, profileMock.uuid); + + await expect(res).resolves.toBeTruthy(); + }); + + it('verifyUserProfile profile not found', async () => { + const userMock = { uuid: 'user uuid' }; + const profileMock = { uuid: 'profile uuid', userUuid: userMock.uuid }; + + (profilesService.findProfileByProfileUuid as jest.Mock).mockResolvedValue( + null, + ); + + const res = usersService.verifyUserProfile(userMock.uuid, profileMock.uuid); + + await expect(res).rejects.toThrow(NotFoundException); + }); + + it('verifyUserProfile profile user not own', async () => { + const userMock = { uuid: 'user uuid' }; + const profileMock = { uuid: 'profile uuid', userUuid: 'other user uuid' }; + + (profilesService.findProfileByProfileUuid as jest.Mock).mockResolvedValue( + profileMock, + ); + + const res = usersService.verifyUserProfile(userMock.uuid, profileMock.uuid); + + await expect(res).rejects.toThrow(ForbiddenException); + }); }); diff --git a/nestjs-BE/server/src/users/users.service.ts b/nestjs-BE/server/src/users/users.service.ts index e68104eb..133c2e9d 100644 --- a/nestjs-BE/server/src/users/users.service.ts +++ b/nestjs-BE/server/src/users/users.service.ts @@ -1,12 +1,20 @@ -import { Injectable } from '@nestjs/common'; +import { + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { CreateUserPrismaDto } from './dto/create-user.dto'; import { Space, User } from '@prisma/client'; import { v4 as uuid } from 'uuid'; +import { ProfilesService } from '../profiles/profiles.service'; @Injectable() export class UsersService { - constructor(private prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly profilesService: ProfilesService, + ) {} async getOrCreateUser(data: CreateUserPrismaDto): Promise<User> { return this.prisma.$transaction(async () => { @@ -43,4 +51,15 @@ export class UsersService { return spaces; } + + async verifyUserProfile( + userUuid: string, + profileUuid: string, + ): Promise<boolean> { + const profile = + await this.profilesService.findProfileByProfileUuid(profileUuid); + if (!profile) throw new NotFoundException(); + if (userUuid !== profile.userUuid) throw new ForbiddenException(); + return true; + } } From 43138bb6f20e921c5f8c949ff3dcd5c39e1cd899 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 16:16:30 +0900 Subject: [PATCH 09/28] =?UTF-8?q?refactor:=20=EC=9C=A0=EC=A0=80=20?= =?UTF-8?q?=ED=94=84=EB=A1=9C=ED=95=84=20=EA=B2=80=EC=A6=9D=20=EB=B6=80?= =?UTF-8?q?=EB=B6=84=20=EA=B5=90=EC=B2=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/spaces/spaces.controller.spec.ts | 76 +++++++------------ .../server/src/spaces/spaces.controller.ts | 30 +++----- nestjs-BE/server/src/spaces/spaces.module.ts | 4 +- 3 files changed, 38 insertions(+), 72 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index bff5fa59..5dcb5eec 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -3,7 +3,6 @@ import { SpacesController } from './spaces.controller'; import { SpacesService } from './spaces.service'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; import { UploadService } from '../upload/upload.service'; -import { ProfilesService } from '../profiles/profiles.service'; import { Profile, Space } from '@prisma/client'; import { BadRequestException, @@ -15,14 +14,15 @@ import { UpdateSpaceRequestDto } from './dto/update-space.dto'; import { CreateSpaceRequestDto } from './dto/create-space.dto'; import { RequestWithUser } from '../utils/interface'; import { ConfigModule, ConfigService } from '@nestjs/config'; +import { UsersService } from '../users/users.service'; describe('SpacesController', () => { let controller: SpacesController; let spacesService: SpacesService; let uploadService: UploadService; - let profilesService: ProfilesService; let configService: ConfigService; let profileSpaceService: ProfileSpaceService; + let usersService: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -45,22 +45,16 @@ describe('SpacesController', () => { joinSpace: jest.fn(), }, }, - { - provide: ProfilesService, - useValue: { - findProfile: jest.fn(), - findProfileByProfileUuid: jest.fn(), - }, - }, + { provide: UsersService, useValue: { verifyUserProfile: jest.fn() } }, ], }).compile(); controller = module.get<SpacesController>(SpacesController); spacesService = module.get<SpacesService>(SpacesService); uploadService = module.get<UploadService>(UploadService); - profilesService = module.get<ProfilesService>(ProfilesService); configService = module.get<ConfigService>(ConfigService); profileSpaceService = module.get<ProfileSpaceService>(ProfileSpaceService); + usersService = module.get<UsersService>(UsersService); }); it('create created', async () => { @@ -77,9 +71,7 @@ describe('SpacesController', () => { } as CreateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest.spyOn(uploadService, 'uploadFile').mockResolvedValue(iconUrlMock); jest.spyOn(spacesService, 'createSpace').mockResolvedValue(spaceMock); @@ -107,8 +99,8 @@ describe('SpacesController', () => { const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(null); + .spyOn(usersService, 'verifyUserProfile') + .mockRejectedValue(new NotFoundException()); const response = controller.create(iconMock, bodyMock, requestMock); @@ -130,8 +122,8 @@ describe('SpacesController', () => { } as CreateSpaceRequestDto; jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + .spyOn(usersService, 'verifyUserProfile') + .mockRejectedValue(new ForbiddenException()); const response = controller.create(iconMock, bodyMock, requestMock); @@ -152,9 +144,7 @@ describe('SpacesController', () => { } as CreateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest.spyOn(spacesService, 'createSpace').mockResolvedValue(spaceMock); const response = controller.create( @@ -188,9 +178,7 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest.spyOn(spacesService, 'findSpace').mockResolvedValue(spaceMock); jest .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') @@ -216,7 +204,7 @@ describe('SpacesController', () => { const response = controller.findOne(spaceMock.uuid, undefined, requestMock); await expect(response).rejects.toThrow(BadRequestException); - expect(profilesService.findProfileByProfileUuid).not.toHaveBeenCalled(); + expect(usersService.verifyUserProfile).not.toHaveBeenCalled(); }); it("findOne profile user doesn't have", async () => { @@ -228,8 +216,8 @@ describe('SpacesController', () => { } as Profile; jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + .spyOn(usersService, 'verifyUserProfile') + .mockRejectedValue(new ForbiddenException()); const response = controller.findOne( spaceMock.uuid, @@ -249,9 +237,7 @@ describe('SpacesController', () => { userUuid: requestMock.user.uuid, } as Profile; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest.spyOn(spacesService, 'findSpace').mockResolvedValue(spaceMock); jest .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') @@ -274,9 +260,7 @@ describe('SpacesController', () => { userUuid: requestMock.user.uuid, } as Profile; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest.spyOn(spacesService, 'findSpace').mockResolvedValue(null); const response = controller.findOne( @@ -297,8 +281,8 @@ describe('SpacesController', () => { } as Profile; jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(null); + .spyOn(usersService, 'verifyUserProfile') + .mockRejectedValue(new NotFoundException()); const response = controller.findOne( spaceMock.uuid, @@ -324,9 +308,7 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') .mockResolvedValue(profileSpaceMock); @@ -366,9 +348,7 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') .mockResolvedValue(profileSpaceMock); @@ -408,9 +388,7 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') .mockResolvedValue(profileSpaceMock); @@ -447,8 +425,8 @@ describe('SpacesController', () => { const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + .spyOn(usersService, 'verifyUserProfile') + .mockRejectedValue(new ForbiddenException()); const response = controller.update( iconMock, @@ -473,9 +451,7 @@ describe('SpacesController', () => { } as Profile; const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; - jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(profileMock); + jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); jest .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') .mockResolvedValue(null); @@ -504,8 +480,8 @@ describe('SpacesController', () => { const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; jest - .spyOn(profilesService, 'findProfileByProfileUuid') - .mockResolvedValue(null); + .spyOn(usersService, 'verifyUserProfile') + .mockRejectedValue(new NotFoundException()); const response = controller.update( iconMock, diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index 9ac17081..b2ad19b8 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -24,8 +24,8 @@ import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; import { UploadService } from '../upload/upload.service'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; import { RequestWithUser } from '../utils/interface'; -import { ProfilesService } from '../profiles/profiles.service'; import { ConfigService } from '@nestjs/config'; +import { UsersService } from '../users/users.service'; @Controller('spaces') @ApiTags('spaces') @@ -34,8 +34,8 @@ export class SpacesController { private readonly spacesService: SpacesService, private readonly uploadService: UploadService, private readonly profileSpaceService: ProfileSpaceService, - private readonly profilesService: ProfilesService, private readonly configService: ConfigService, + private readonly usersService: UsersService, ) {} @Post() @@ -74,19 +74,19 @@ export class SpacesController { @Req() req: RequestWithUser, ) { if (!createSpaceDto.profileUuid) throw new BadRequestException(); - const profile = await this.profilesService.findProfileByProfileUuid( + await this.usersService.verifyUserProfile( + req.user.uuid, createSpaceDto.profileUuid, ); - if (!profile) throw new NotFoundException(); - if (req.user.uuid !== profile.userUuid) { - throw new ForbiddenException(); - } const iconUrl = icon ? await this.uploadService.uploadFile(icon) : this.configService.get<string>('APP_ICON_URL'); createSpaceDto.icon = iconUrl; const space = await this.spacesService.createSpace(createSpaceDto); - await this.profileSpaceService.joinSpace(profile.uuid, space.uuid); + await this.profileSpaceService.joinSpace( + createSpaceDto.profileUuid, + space.uuid, + ); return { statusCode: HttpStatus.CREATED, message: 'Created', data: space }; } @@ -119,12 +119,7 @@ export class SpacesController { @Req() req: RequestWithUser, ) { if (!profileUuid) throw new BadRequestException(); - const profile = - await this.profilesService.findProfileByProfileUuid(profileUuid); - if (!profile) throw new NotFoundException(); - if (req.user.uuid !== profile.userUuid) { - throw new ForbiddenException(); - } + await this.usersService.verifyUserProfile(req.user.uuid, profileUuid); const space = await this.spacesService.findSpace(spaceUuid); if (!space) throw new NotFoundException(); const profileSpace = @@ -168,12 +163,7 @@ export class SpacesController { @Req() req: RequestWithUser, ) { if (!profileUuid) throw new BadRequestException(); - const profile = - await this.profilesService.findProfileByProfileUuid(profileUuid); - if (!profile) throw new NotFoundException(); - if (req.user.uuid !== profile.userUuid) { - throw new ForbiddenException(); - } + await this.usersService.verifyUserProfile(req.user.uuid, profileUuid); const profileSpace = await this.profileSpaceService.findProfileSpaceByBothUuid( profileUuid, diff --git a/nestjs-BE/server/src/spaces/spaces.module.ts b/nestjs-BE/server/src/spaces/spaces.module.ts index 52c1bf25..3bd19f1d 100644 --- a/nestjs-BE/server/src/spaces/spaces.module.ts +++ b/nestjs-BE/server/src/spaces/spaces.module.ts @@ -3,10 +3,10 @@ import { SpacesService } from './spaces.service'; import { SpacesController } from './spaces.controller'; import { UploadModule } from '../upload/upload.module'; import { ProfileSpaceModule } from '../profile-space/profile-space.module'; -import { ProfilesModule } from '../profiles/profiles.module'; +import { UsersModule } from '../users/users.module'; @Module({ - imports: [forwardRef(() => ProfileSpaceModule), ProfilesModule, UploadModule], + imports: [forwardRef(() => ProfileSpaceModule), UploadModule, UsersModule], controllers: [SpacesController], providers: [SpacesService], exports: [SpacesService], From 9decbf613699fbe71d99bc3b683367fe00e81005 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 17:57:37 +0900 Subject: [PATCH 10/28] =?UTF-8?q?refactor:=20mocking=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/spaces/spaces.service.spec.ts | 21 ++++++++++--------- 1 file changed, 11 insertions(+), 10 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.service.spec.ts b/nestjs-BE/server/src/spaces/spaces.service.spec.ts index 4efe6e2a..4c508469 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.spec.ts @@ -25,7 +25,8 @@ describe('SpacesService', () => { it('updateSpace updated space', async () => { const data = { name: 'new space name', icon: 'new space icon' }; const spaceMock = { uuid: 'space uuid', ...data }; - jest.spyOn(prisma.space, 'update').mockResolvedValue(spaceMock); + + (prisma.space.update as jest.Mock).mockResolvedValue(spaceMock); const space = spacesService.updateSpace('space uuid', data); @@ -34,14 +35,13 @@ describe('SpacesService', () => { it('updateSpace fail', async () => { const data = { name: 'new space name', icon: 'new space icon' }; - jest - .spyOn(prisma.space, 'update') - .mockRejectedValue( - new PrismaClientKnownRequestError( - 'An operation failed because it depends on one or more records that were required but not found. Record to update not found.', - { code: 'P2025', clientVersion: '' }, - ), - ); + + (prisma.space.update as jest.Mock).mockRejectedValue( + new PrismaClientKnownRequestError('', { + code: 'P2025', + clientVersion: '', + }), + ); const space = spacesService.updateSpace('space uuid', data); @@ -50,7 +50,8 @@ describe('SpacesService', () => { it('updateSpace fail', async () => { const data = { name: 'new space name', icon: 'new space icon' }; - jest.spyOn(prisma.space, 'update').mockRejectedValue(new Error()); + + (prisma.space.update as jest.Mock).mockRejectedValue(new Error()); const space = spacesService.updateSpace('space uuid', data); From dc0f1e2db9c0395c9030a2b5c3c4e78d6fa22c0f Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 18:53:33 +0900 Subject: [PATCH 11/28] =?UTF-8?q?feat:=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EA=B4=80=EB=A0=A8=20=EB=8F=99=EC=9E=91=20=EC=84=9C?= =?UTF-8?q?=EB=B9=84=EC=8A=A4=EB=A1=9C=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile-space/profile-space.service.ts | 18 ++ .../server/src/spaces/spaces.service.spec.ts | 213 +++++++++++++++++- nestjs-BE/server/src/spaces/spaces.service.ts | 78 ++++++- 3 files changed, 305 insertions(+), 4 deletions(-) diff --git a/nestjs-BE/server/src/profile-space/profile-space.service.ts b/nestjs-BE/server/src/profile-space/profile-space.service.ts index 55c6c1da..a1e4769e 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.service.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.service.ts @@ -6,6 +6,24 @@ import { Prisma, ProfileSpace } from '@prisma/client'; export class ProfileSpaceService { constructor(private readonly prisma: PrismaService) {} + async createProfileSpace( + profileUuid: string, + spaceUuid: string, + ): Promise<ProfileSpace | null> { + return this.prisma.profileSpace.create({ + data: { spaceUuid, profileUuid }, + }); + } + + async deleteProfileSpace( + profileUuid: string, + spaceUuid: string, + ): Promise<ProfileSpace | null> { + return this.prisma.profileSpace.delete({ + where: { spaceUuid_profileUuid: { spaceUuid, profileUuid } }, + }); + } + async findProfileSpacesByProfileUuid( profileUuid: string, ): Promise<ProfileSpace[]> { diff --git a/nestjs-BE/server/src/spaces/spaces.service.spec.ts b/nestjs-BE/server/src/spaces/spaces.service.spec.ts index 4c508469..42f43896 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.spec.ts @@ -1,11 +1,20 @@ +import { + ConflictException, + ForbiddenException, + NotFoundException, +} from '@nestjs/common'; import { Test, TestingModule } from '@nestjs/testing'; import { SpacesService } from './spaces.service'; import { PrismaService } from '../prisma/prisma.service'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; +import { ProfileSpaceService } from '../profile-space/profile-space.service'; +import { UsersService } from '../users/users.service'; describe('SpacesService', () => { let spacesService: SpacesService; let prisma: PrismaService; + let profileSpaceService: ProfileSpaceService; + let usersService: UsersService; beforeEach(async () => { const module: TestingModule = await Test.createTestingModule({ @@ -13,13 +22,30 @@ describe('SpacesService', () => { SpacesService, { provide: PrismaService, - useValue: { space: { update: jest.fn() } }, + useValue: { + space: { update: jest.fn() }, + profile: { findMany: jest.fn() }, + }, + }, + { + provide: ProfileSpaceService, + useValue: { + createProfileSpace: jest.fn(), + deleteProfileSpace: jest.fn(), + isSpaceEmpty: jest.fn(), + }, + }, + { + provide: UsersService, + useValue: { verifyUserProfile: jest.fn() }, }, ], }).compile(); spacesService = module.get<SpacesService>(SpacesService); prisma = module.get<PrismaService>(PrismaService); + profileSpaceService = module.get<ProfileSpaceService>(ProfileSpaceService); + usersService = module.get<UsersService>(UsersService); }); it('updateSpace updated space', async () => { @@ -57,4 +83,189 @@ describe('SpacesService', () => { await expect(space).rejects.toThrow(Error); }); + + it('joinSpace', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + const res = spacesService.joinSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).resolves.toBeUndefined(); + }); + + it('joinSpace profile not found', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new NotFoundException(), + ); + + const res = spacesService.joinSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(NotFoundException); + }); + + it('joinSpace profile user not own', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new ForbiddenException(), + ); + + const res = spacesService.joinSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(ForbiddenException); + }); + + it('joinSpace conflict', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (profileSpaceService.createProfileSpace as jest.Mock).mockRejectedValue( + new PrismaClientKnownRequestError('', { + code: 'P2002', + clientVersion: '', + }), + ); + + const res = spacesService.joinSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(ConflictException); + }); + + it('joinSpace space not found', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (profileSpaceService.createProfileSpace as jest.Mock).mockRejectedValue( + new PrismaClientKnownRequestError('', { + code: 'P2003', + clientVersion: '', + }), + ); + + const res = spacesService.joinSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(ForbiddenException); + }); + + it('leaveSpace', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + jest.spyOn(spacesService, 'deleteSpace').mockResolvedValue(null); + + const res = spacesService.leaveSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).resolves.toBeUndefined(); + }); + + it('leaveSpace space delete fail', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + jest.spyOn(spacesService, 'deleteSpace').mockRejectedValue( + new PrismaClientKnownRequestError('', { + code: 'P2025', + clientVersion: '', + }), + ); + + const res = spacesService.leaveSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).resolves.toBeUndefined(); + }); + + it('leaveSpace profile not found', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new NotFoundException(), + ); + jest.spyOn(spacesService, 'deleteSpace').mockResolvedValue(null); + + const res = spacesService.leaveSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(NotFoundException); + }); + + it('leaveSpace profile user not own', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new ForbiddenException(), + ); + jest.spyOn(spacesService, 'deleteSpace').mockResolvedValue(null); + + const res = spacesService.leaveSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(ForbiddenException); + }); + + it('leaveSpace profileSpace not found', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (profileSpaceService.deleteProfileSpace as jest.Mock).mockRejectedValue( + new PrismaClientKnownRequestError('', { + code: 'P2025', + clientVersion: '', + }), + ); + jest.spyOn(spacesService, 'deleteSpace').mockResolvedValue(null); + + const res = spacesService.leaveSpace(userUuid, profileUuid, spaceUuid); + + await expect(res).rejects.toThrow(NotFoundException); + }); + + it('findProfilesInSpace profile not found', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new NotFoundException(), + ); + + const res = spacesService.findProfilesInSpace( + userUuid, + profileUuid, + spaceUuid, + ); + + await expect(res).rejects.toThrow(NotFoundException); + }); + + it('findProfilesInSpace profile user not own', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new ForbiddenException(), + ); + + const res = spacesService.findProfilesInSpace( + userUuid, + profileUuid, + spaceUuid, + ); + + await expect(res).rejects.toThrow(ForbiddenException); + }); }); diff --git a/nestjs-BE/server/src/spaces/spaces.service.ts b/nestjs-BE/server/src/spaces/spaces.service.ts index e2b200a0..47bd1cf6 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.ts @@ -1,13 +1,24 @@ -import { Injectable } from '@nestjs/common'; +import { + ConflictException, + ForbiddenException, + Injectable, + NotFoundException, +} from '@nestjs/common'; import { PrismaService } from '../prisma/prisma.service'; import { UpdateSpacePrismaDto } from './dto/update-space.dto'; -import { Prisma, Space } from '@prisma/client'; +import { Prisma, Profile, Space } from '@prisma/client'; import { CreateSpacePrismaDto } from './dto/create-space.dto'; import { v4 as uuid } from 'uuid'; +import { ProfileSpaceService } from '../profile-space/profile-space.service'; +import { UsersService } from '../users/users.service'; @Injectable() export class SpacesService { - constructor(protected prisma: PrismaService) {} + constructor( + private readonly prisma: PrismaService, + private readonly profileSpaceService: ProfileSpaceService, + private readonly usersService: UsersService, + ) {} async findSpace(spaceUuid: string): Promise<Space | null> { return this.prisma.space.findUnique({ where: { uuid: spaceUuid } }); @@ -48,4 +59,65 @@ export class SpacesService { async deleteSpace(spaceUuid: string): Promise<Space> { return this.prisma.space.delete({ where: { uuid: spaceUuid } }); } + + async joinSpace( + userUuid: string, + profileUuid: string, + spaceUuid: string, + ): Promise<void> { + await this.usersService.verifyUserProfile(userUuid, profileUuid); + try { + await this.profileSpaceService.createProfileSpace(profileUuid, spaceUuid); + } catch (err) { + if (err instanceof Prisma.PrismaClientKnownRequestError) { + switch (err.code) { + case 'P2002': + throw new ConflictException(); + case 'P2003': + throw new ForbiddenException(); + default: + throw err; + } + } else { + throw err; + } + } + } + + async leaveSpace( + userUuid: string, + profileUuid: string, + spaceUuid: string, + ): Promise<void> { + await this.usersService.verifyUserProfile(userUuid, profileUuid); + try { + await this.profileSpaceService.deleteProfileSpace(profileUuid, spaceUuid); + } catch (err) { + if (err instanceof Prisma.PrismaClientKnownRequestError) { + switch (err.code) { + case 'P2025': + throw new NotFoundException(); + default: + throw err; + } + } else { + throw err; + } + } + const isSpaceEmpty = await this.profileSpaceService.isSpaceEmpty(spaceUuid); + try { + if (!isSpaceEmpty) await this.deleteSpace(spaceUuid); + } catch (err) {} + } + + async findProfilesInSpace( + userUuid: string, + profileUuid: string, + spaceUuid: string, + ): Promise<Profile[]> { + await this.usersService.verifyUserProfile(userUuid, profileUuid); + return this.prisma.profile.findMany({ + where: { spaces: { some: { spaceUuid } } }, + }); + } } From cad896f792e60547873901f43b7997a6d7ed9da4 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 20:10:15 +0900 Subject: [PATCH 12/28] =?UTF-8?q?refactor:=20=EB=B9=84=EB=8F=99=EA=B8=B0?= =?UTF-8?q?=20=EB=8F=99=EC=9E=91=20=EA=B0=9C=EC=84=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/users.e2e-spec.ts | 24 ++++++++++-------------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/nestjs-BE/server/test/users.e2e-spec.ts b/nestjs-BE/server/test/users.e2e-spec.ts index 1f55a7a5..c3d1955f 100644 --- a/nestjs-BE/server/test/users.e2e-spec.ts +++ b/nestjs-BE/server/test/users.e2e-spec.ts @@ -64,20 +64,16 @@ describe('UsersController (e2e)', () => { }, }); const spacePromises = Array.from({ length: SPACE_NUMBER }, async () => { - return prisma.space - .create({ - data: { uuid: uuid(), name: 'test space', icon: 'test icon' }, - }) - .then(async (space) => { - return prisma.profileSpace - .create({ - data: { - profileUuid: profile.uuid, - spaceUuid: space.uuid, - }, - }) - .then(() => space); - }); + const space = await prisma.space.create({ + data: { uuid: uuid(), name: 'test space', icon: 'test icon' }, + }); + await prisma.profileSpace.create({ + data: { + profileUuid: profile.uuid, + spaceUuid: space.uuid, + }, + }); + return space; }); const spaces = await Promise.all(spacePromises); From c892ca55a6faa97e6acd546ccb2309259ba335b3 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 20:35:40 +0900 Subject: [PATCH 13/28] =?UTF-8?q?test:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile-space.service.spec.ts | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) create mode 100644 nestjs-BE/server/src/profile-space/profile-space.service.spec.ts diff --git a/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts b/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts new file mode 100644 index 00000000..df6aefae --- /dev/null +++ b/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts @@ -0,0 +1,46 @@ +import { Test, TestingModule } from '@nestjs/testing'; +import { ProfileSpaceService } from './profile-space.service'; +import { PrismaService } from '../prisma/prisma.service'; + +describe('ProfileSpaceService', () => { + let profileSpaceService: ProfileSpaceService; + let prisma: PrismaService; + + beforeEach(async () => { + const module: TestingModule = await Test.createTestingModule({ + providers: [ + ProfileSpaceService, + { + provide: PrismaService, + useValue: { profileSpace: { findFirst: jest.fn() } }, + }, + ], + }).compile(); + + profileSpaceService = module.get<ProfileSpaceService>(ProfileSpaceService); + prisma = module.get<PrismaService>(PrismaService); + }); + + it('isSpaceEmpty empty', async () => { + const spaceUuid = 'space uuid'; + + (prisma.profileSpace.findFirst as jest.Mock).mockResolvedValue(null); + + const isSpaceEmpty = profileSpaceService.isSpaceEmpty(spaceUuid); + + await expect(isSpaceEmpty).resolves.toBeTruthy(); + }); + + it('isSpaceEmpty not empty', async () => { + const spaceUuid = 'space uuid'; + const profileSpace = 'profile space'; + + (prisma.profileSpace.findFirst as jest.Mock).mockResolvedValue( + profileSpace, + ); + + const isSpaceEmpty = profileSpaceService.isSpaceEmpty(spaceUuid); + + await expect(isSpaceEmpty).resolves.toBeFalsy(); + }); +}); From 74081fe4f47985e7f16b6618d950716e5e4ddcc6 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 20:37:55 +0900 Subject: [PATCH 14/28] =?UTF-8?q?remove:=20=EB=B6=88=ED=95=84=EC=9A=94=20?= =?UTF-8?q?=ED=8C=8C=EC=9D=BC=20=EC=82=AD=EC=A0=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/profiles/dto/profile-space.dto.ts | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 nestjs-BE/server/src/profiles/dto/profile-space.dto.ts diff --git a/nestjs-BE/server/src/profiles/dto/profile-space.dto.ts b/nestjs-BE/server/src/profiles/dto/profile-space.dto.ts deleted file mode 100644 index 19fad8d4..00000000 --- a/nestjs-BE/server/src/profiles/dto/profile-space.dto.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { ApiProperty } from '@nestjs/swagger'; - -export class ProfileSpaceDto { - @ApiProperty({ - example: 'profile-uuid-123', - description: 'UUID of the profile', - }) - profile_uuid: string; - - @ApiProperty({ example: 'space-uuid-456', description: 'UUID of the space' }) - space_uuid: string; -} From 3fa19bf1fb8777cf48ad7deaa9440858bacf4bbb Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 21:01:32 +0900 Subject: [PATCH 15/28] =?UTF-8?q?refactor:=20Mocking=20=EB=B0=A9=EC=8B=9D?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/spaces/spaces.controller.spec.ts | 112 +++++++++--------- 1 file changed, 56 insertions(+), 56 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index 5dcb5eec..ca374aa1 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -71,9 +71,9 @@ describe('SpacesController', () => { } as CreateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest.spyOn(uploadService, 'uploadFile').mockResolvedValue(iconUrlMock); - jest.spyOn(spacesService, 'createSpace').mockResolvedValue(spaceMock); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + (uploadService.uploadFile as jest.Mock).mockResolvedValue(iconUrlMock); + (spacesService.createSpace as jest.Mock).mockResolvedValue(spaceMock); const response = controller.create(iconMock, bodyMock, requestMock); @@ -98,9 +98,9 @@ describe('SpacesController', () => { } as CreateSpaceRequestDto; const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; - jest - .spyOn(usersService, 'verifyUserProfile') - .mockRejectedValue(new NotFoundException()); + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new NotFoundException(), + ); const response = controller.create(iconMock, bodyMock, requestMock); @@ -121,9 +121,9 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, } as CreateSpaceRequestDto; - jest - .spyOn(usersService, 'verifyUserProfile') - .mockRejectedValue(new ForbiddenException()); + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new ForbiddenException(), + ); const response = controller.create(iconMock, bodyMock, requestMock); @@ -144,8 +144,8 @@ describe('SpacesController', () => { } as CreateSpaceRequestDto; const spaceMock = { uuid: 'space uuid' } as Space; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest.spyOn(spacesService, 'createSpace').mockResolvedValue(spaceMock); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + (spacesService.createSpace as jest.Mock).mockResolvedValue(spaceMock); const response = controller.create( null as unknown as Express.Multer.File, @@ -178,11 +178,11 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest.spyOn(spacesService, 'findSpace').mockResolvedValue(spaceMock); - jest - .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') - .mockResolvedValue(profileSpaceMock); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + (spacesService.findSpace as jest.Mock).mockResolvedValue(spaceMock); + ( + profileSpaceService.findProfileSpaceByBothUuid as jest.Mock + ).mockResolvedValue(profileSpaceMock); const response = controller.findOne( spaceMock.uuid, @@ -215,9 +215,9 @@ describe('SpacesController', () => { userUuid: 'wrong user uuid', } as Profile; - jest - .spyOn(usersService, 'verifyUserProfile') - .mockRejectedValue(new ForbiddenException()); + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new ForbiddenException(), + ); const response = controller.findOne( spaceMock.uuid, @@ -237,11 +237,11 @@ describe('SpacesController', () => { userUuid: requestMock.user.uuid, } as Profile; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest.spyOn(spacesService, 'findSpace').mockResolvedValue(spaceMock); - jest - .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') - .mockResolvedValue(null); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + (spacesService.findSpace as jest.Mock).mockResolvedValue(spaceMock); + ( + profileSpaceService.findProfileSpaceByBothUuid as jest.Mock + ).mockResolvedValue(null); const response = controller.findOne( spaceMock.uuid, @@ -260,8 +260,8 @@ describe('SpacesController', () => { userUuid: requestMock.user.uuid, } as Profile; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest.spyOn(spacesService, 'findSpace').mockResolvedValue(null); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + (spacesService.findSpace as jest.Mock).mockResolvedValue(null); const response = controller.findOne( spaceMock.uuid, @@ -280,9 +280,9 @@ describe('SpacesController', () => { userUuid: requestMock.user.uuid, } as Profile; - jest - .spyOn(usersService, 'verifyUserProfile') - .mockRejectedValue(new NotFoundException()); + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new NotFoundException(), + ); const response = controller.findOne( spaceMock.uuid, @@ -308,12 +308,12 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest - .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') - .mockResolvedValue(profileSpaceMock); - jest.spyOn(uploadService, 'uploadFile').mockResolvedValue(iconUrlMock); - jest.spyOn(spacesService, 'updateSpace').mockResolvedValue(spaceMock); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + ( + profileSpaceService.findProfileSpaceByBothUuid as jest.Mock + ).mockResolvedValue(profileSpaceMock); + (uploadService.uploadFile as jest.Mock).mockResolvedValue(iconUrlMock); + (spacesService.updateSpace as jest.Mock).mockResolvedValue(spaceMock); const response = controller.update( iconMock, @@ -348,11 +348,11 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest - .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') - .mockResolvedValue(profileSpaceMock); - jest.spyOn(spacesService, 'updateSpace').mockResolvedValue(spaceMock); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + ( + profileSpaceService.findProfileSpaceByBothUuid as jest.Mock + ).mockResolvedValue(profileSpaceMock); + (spacesService.updateSpace as jest.Mock).mockResolvedValue(spaceMock); const response = controller.update( null as unknown as Express.Multer.File, @@ -388,12 +388,12 @@ describe('SpacesController', () => { profileUuid: profileMock.uuid, }; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest - .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') - .mockResolvedValue(profileSpaceMock); - jest.spyOn(spacesService, 'updateSpace').mockResolvedValue(spaceMock); - jest.spyOn(uploadService, 'uploadFile').mockResolvedValue(iconUrlMock); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + ( + profileSpaceService.findProfileSpaceByBothUuid as jest.Mock + ).mockResolvedValue(profileSpaceMock); + (spacesService.updateSpace as jest.Mock).mockResolvedValue(spaceMock); + (uploadService.uploadFile as jest.Mock).mockResolvedValue(iconUrlMock); const response = controller.update( iconMock, @@ -424,9 +424,9 @@ describe('SpacesController', () => { } as Profile; const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; - jest - .spyOn(usersService, 'verifyUserProfile') - .mockRejectedValue(new ForbiddenException()); + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new ForbiddenException(), + ); const response = controller.update( iconMock, @@ -451,10 +451,10 @@ describe('SpacesController', () => { } as Profile; const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; - jest.spyOn(usersService, 'verifyUserProfile').mockResolvedValue(true); - jest - .spyOn(profileSpaceService, 'findProfileSpaceByBothUuid') - .mockResolvedValue(null); + (usersService.verifyUserProfile as jest.Mock).mockResolvedValue(true); + ( + profileSpaceService.findProfileSpaceByBothUuid as jest.Mock + ).mockResolvedValue(null); const response = controller.update( iconMock, @@ -479,9 +479,9 @@ describe('SpacesController', () => { } as Profile; const bodyMock = { name: 'new space name' } as UpdateSpaceRequestDto; - jest - .spyOn(usersService, 'verifyUserProfile') - .mockRejectedValue(new NotFoundException()); + (usersService.verifyUserProfile as jest.Mock).mockRejectedValue( + new NotFoundException(), + ); const response = controller.update( iconMock, From b3f296dcf4ac97bc5ee93e9bb98228d3f26a25b6 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 21:33:35 +0900 Subject: [PATCH 16/28] =?UTF-8?q?feat:=20joinSpace=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/src/spaces/dto/join-space.dto.ts | 12 +++++ .../src/spaces/spaces.controller.spec.ts | 21 +++++++++ .../server/src/spaces/spaces.controller.ts | 47 +++++++++++++++++++ .../server/src/spaces/spaces.service.spec.ts | 6 ++- nestjs-BE/server/src/spaces/spaces.service.ts | 3 +- 5 files changed, 87 insertions(+), 2 deletions(-) create mode 100644 nestjs-BE/server/src/spaces/dto/join-space.dto.ts diff --git a/nestjs-BE/server/src/spaces/dto/join-space.dto.ts b/nestjs-BE/server/src/spaces/dto/join-space.dto.ts new file mode 100644 index 00000000..462a053e --- /dev/null +++ b/nestjs-BE/server/src/spaces/dto/join-space.dto.ts @@ -0,0 +1,12 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Expose } from 'class-transformer'; +import { IsNotEmpty, IsString } from 'class-validator'; +import { v4 as uuid } from 'uuid'; + +export class JoinSpaceRequestDto { + @IsString() + @IsNotEmpty() + @Expose({ name: 'profile_uuid' }) + @ApiProperty({ example: uuid(), description: 'Profile uuid' }) + profileUuid: string; +} diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index ca374aa1..a6501ded 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -35,6 +35,7 @@ describe('SpacesController', () => { createSpace: jest.fn(), findSpace: jest.fn(), updateSpace: jest.fn(), + joinSpace: jest.fn(), }, }, { provide: UploadService, useValue: { uploadFile: jest.fn() } }, @@ -495,4 +496,24 @@ describe('SpacesController', () => { expect(uploadService.uploadFile).not.toHaveBeenCalled(); expect(spacesService.updateSpace).not.toHaveBeenCalled(); }); + + it('joinSpace', async () => { + const spaceMock = { uuid: 'space uuid' }; + const bodyMock = { profileUuid: 'profile uuid' }; + const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; + + (spacesService.joinSpace as jest.Mock).mockResolvedValue(spaceMock); + + const response = controller.joinSpace( + spaceMock.uuid, + bodyMock, + requestMock, + ); + + await expect(response).resolves.toEqual({ + statusCode: HttpStatus.CREATED, + message: 'Created', + data: spaceMock, + }); + }); }); diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index b2ad19b8..e23800b9 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -26,6 +26,7 @@ import { ProfileSpaceService } from '../profile-space/profile-space.service'; import { RequestWithUser } from '../utils/interface'; import { ConfigService } from '@nestjs/config'; import { UsersService } from '../users/users.service'; +import { JoinSpaceRequestDto } from './dto/join-space.dto'; @Controller('spaces') @ApiTags('spaces') @@ -180,4 +181,50 @@ export class SpacesController { if (!space) throw new NotFoundException(); return { statusCode: HttpStatus.OK, message: 'OK', data: space }; } + + @Post(':space_uuid/join') + @ApiOperation({ summary: 'Join space' }) + @ApiResponse({ + status: HttpStatus.CREATED, + description: 'Join data has been successfully created.', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Profile uuid needed.', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: 'User not logged in.', + }) + @ApiResponse({ + status: HttpStatus.FORBIDDEN, + description: 'Profile user not own.', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Profile not found.', + }) + @ApiResponse({ + status: HttpStatus.CONFLICT, + description: 'Conflict. You have already joined the space.', + }) + async joinSpace( + @Param('space_uuid') spaceUuid: string, + @Body( + new ValidationPipe({ + transform: true, + whitelist: true, + disableErrorMessages: true, + }), + ) + joinSpaceDto: JoinSpaceRequestDto, + @Req() req: RequestWithUser, + ) { + const space = await this.spacesService.joinSpace( + req.user.uuid, + joinSpaceDto.profileUuid, + spaceUuid, + ); + return { statusCode: HttpStatus.CREATED, message: 'Created', data: space }; + } } diff --git a/nestjs-BE/server/src/spaces/spaces.service.spec.ts b/nestjs-BE/server/src/spaces/spaces.service.spec.ts index 42f43896..16355fc3 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.spec.ts @@ -9,6 +9,7 @@ import { PrismaService } from '../prisma/prisma.service'; import { PrismaClientKnownRequestError } from '@prisma/client/runtime/library'; import { ProfileSpaceService } from '../profile-space/profile-space.service'; import { UsersService } from '../users/users.service'; +import { Space } from '@prisma/client'; describe('SpacesService', () => { let spacesService: SpacesService; @@ -88,10 +89,13 @@ describe('SpacesService', () => { const userUuid = 'user uuid'; const profileUuid = 'profile uuid'; const spaceUuid = 'space uuid'; + const space = { uuid: spaceUuid } as Space; + + jest.spyOn(spacesService, 'findSpace').mockResolvedValue(space); const res = spacesService.joinSpace(userUuid, profileUuid, spaceUuid); - await expect(res).resolves.toBeUndefined(); + await expect(res).resolves.toEqual(space); }); it('joinSpace profile not found', async () => { diff --git a/nestjs-BE/server/src/spaces/spaces.service.ts b/nestjs-BE/server/src/spaces/spaces.service.ts index 47bd1cf6..26152155 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.ts @@ -64,7 +64,7 @@ export class SpacesService { userUuid: string, profileUuid: string, spaceUuid: string, - ): Promise<void> { + ): Promise<Space> { await this.usersService.verifyUserProfile(userUuid, profileUuid); try { await this.profileSpaceService.createProfileSpace(profileUuid, spaceUuid); @@ -82,6 +82,7 @@ export class SpacesService { throw err; } } + return this.findSpace(spaceUuid); } async leaveSpace( From 268392bfa3fb1bc531014b928fb0e23ead894686 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 21:41:40 +0900 Subject: [PATCH 17/28] =?UTF-8?q?feat:=20leaveSpace=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/spaces/spaces.controller.spec.ts | 20 +++++++++++ .../server/src/spaces/spaces.controller.ts | 34 +++++++++++++++++++ 2 files changed, 54 insertions(+) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index a6501ded..74a5d510 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -36,6 +36,7 @@ describe('SpacesController', () => { findSpace: jest.fn(), updateSpace: jest.fn(), joinSpace: jest.fn(), + leaveSpace: jest.fn(), }, }, { provide: UploadService, useValue: { uploadFile: jest.fn() } }, @@ -516,4 +517,23 @@ describe('SpacesController', () => { data: spaceMock, }); }); + + it('leaveSpace', async () => { + const spaceMock = { uuid: 'space uuid' }; + const profileMock = { uuid: 'profile uuid' }; + const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; + + (spacesService.leaveSpace as jest.Mock).mockResolvedValue(undefined); + + const response = controller.leaveSpace( + spaceMock.uuid, + profileMock.uuid, + requestMock, + ); + + await expect(response).resolves.toEqual({ + statusCode: HttpStatus.NO_CONTENT, + message: 'No Content', + }); + }); }); diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index e23800b9..30326d0a 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -15,6 +15,8 @@ import { ForbiddenException, Query, BadRequestException, + Delete, + HttpCode, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { SpacesService } from './spaces.service'; @@ -227,4 +229,36 @@ export class SpacesController { ); return { statusCode: HttpStatus.CREATED, message: 'Created', data: space }; } + + @Delete(':space_uuid/profiles/:profile_uuid') + @HttpCode(HttpStatus.NO_CONTENT) + @ApiOperation({ summary: 'Leave space' }) + @ApiResponse({ + status: HttpStatus.NO_CONTENT, + description: 'Successfully left the space.', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Profile uuid needed.', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: 'User not logged in.', + }) + @ApiResponse({ + status: HttpStatus.FORBIDDEN, + description: 'Profile user not own.', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Profile not found. Profile not joined space.', + }) + async leaveSpace( + @Param('space_uuid') spaceUuid: string, + @Param('profile_uuid') profileUuid: string, + @Req() req: RequestWithUser, + ) { + await this.spacesService.leaveSpace(req.user.uuid, profileUuid, spaceUuid); + return { statusCode: HttpStatus.NO_CONTENT, message: 'No Content' }; + } } From baf605b8c095ceff47c63f14d19f12f6dbab28fe Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 22:01:53 +0900 Subject: [PATCH 18/28] =?UTF-8?q?feat:=20findProfilesInSpace=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/spaces/spaces.controller.spec.ts | 39 +++++++++++++++++++ .../server/src/spaces/spaces.controller.ts | 37 ++++++++++++++++++ 2 files changed, 76 insertions(+) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index 74a5d510..b7745766 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -37,6 +37,7 @@ describe('SpacesController', () => { updateSpace: jest.fn(), joinSpace: jest.fn(), leaveSpace: jest.fn(), + findProfilesInSpace: jest.fn(), }, }, { provide: UploadService, useValue: { uploadFile: jest.fn() } }, @@ -536,4 +537,42 @@ describe('SpacesController', () => { message: 'No Content', }); }); + + it('findProfilesInSpace', async () => { + const spaceMock = { uuid: 'space uuid' }; + const profileMock = { uuid: 'profile uuid' }; + const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; + const profilesMock = []; + + (spacesService.findProfilesInSpace as jest.Mock).mockResolvedValue( + profilesMock, + ); + + const response = controller.findProfilesInSpace( + spaceMock.uuid, + profileMock.uuid, + requestMock, + ); + + await expect(response).resolves.toEqual({ + statusCode: HttpStatus.OK, + message: 'OK', + data: profilesMock, + }); + }); + + it('findProfilesInSpace space uuid needed', async () => { + const spaceMock = { uuid: 'space uuid' }; + const requestMock = { user: { uuid: 'user uuid' } } as RequestWithUser; + + (spacesService.findProfilesInSpace as jest.Mock).mockResolvedValue([]); + + const response = controller.findProfilesInSpace( + spaceMock.uuid, + undefined, + requestMock, + ); + + await expect(response).rejects.toThrow(BadRequestException); + }); }); diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index 30326d0a..cf2a7170 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -261,4 +261,41 @@ export class SpacesController { await this.spacesService.leaveSpace(req.user.uuid, profileUuid, spaceUuid); return { statusCode: HttpStatus.NO_CONTENT, message: 'No Content' }; } + + @Get(':space_uuid/profiles') + @Header('Cache-Control', 'no-store') + @ApiOperation({ summary: 'Get profiles joined space.' }) + @ApiResponse({ + status: HttpStatus.OK, + description: 'Successfully get profiles.', + }) + @ApiResponse({ + status: HttpStatus.BAD_REQUEST, + description: 'Profile uuid needed.', + }) + @ApiResponse({ + status: HttpStatus.UNAUTHORIZED, + description: 'User not logged in.', + }) + @ApiResponse({ + status: HttpStatus.FORBIDDEN, + description: 'Profile user not own.', + }) + @ApiResponse({ + status: HttpStatus.NOT_FOUND, + description: 'Profile not found. Profile not joined space.', + }) + async findProfilesInSpace( + @Param('space_uuid') spaceUuid: string, + @Query('profile_uuid') profileUuid: string, + @Req() req: RequestWithUser, + ) { + if (!profileUuid) throw new BadRequestException(); + const profiles = await this.spacesService.findProfilesInSpace( + req.user.uuid, + profileUuid, + spaceUuid, + ); + return { statusCode: HttpStatus.OK, message: 'OK', data: profiles }; + } } From 418a2c07fdc657786c395adf04f382244891c79f Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Sun, 6 Oct 2024 22:27:06 +0900 Subject: [PATCH 19/28] =?UTF-8?q?feat:=20=EB=AA=A8=EB=93=88=EC=97=90=20?= =?UTF-8?q?=EB=A7=9E=EC=A7=80=20=EC=95=8A=EC=9D=80=20controller=20?= =?UTF-8?q?=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../dto/create-profile-space.dto.ts | 14 -- .../dto/update-profile-space.dto.ts | 6 - .../profile-space/profile-space.controller.ts | 126 ------------------ .../src/profile-space/profile-space.module.ts | 7 +- .../profile-space/profile-space.service.ts | 38 ------ nestjs-BE/server/src/spaces/spaces.module.ts | 4 +- 6 files changed, 4 insertions(+), 191 deletions(-) delete mode 100644 nestjs-BE/server/src/profile-space/dto/create-profile-space.dto.ts delete mode 100644 nestjs-BE/server/src/profile-space/dto/update-profile-space.dto.ts delete mode 100644 nestjs-BE/server/src/profile-space/profile-space.controller.ts diff --git a/nestjs-BE/server/src/profile-space/dto/create-profile-space.dto.ts b/nestjs-BE/server/src/profile-space/dto/create-profile-space.dto.ts deleted file mode 100644 index fdd16f5c..00000000 --- a/nestjs-BE/server/src/profile-space/dto/create-profile-space.dto.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { IsNotEmpty, IsString } from 'class-validator'; -import { ApiProperty } from '@nestjs/swagger'; - -export class CreateProfileSpaceDto { - @ApiProperty({ - example: 'space uuid', - description: 'Space UUID', - }) - @IsNotEmpty() - @IsString() - space_uuid: string; - - profile_uuid: string; -} diff --git a/nestjs-BE/server/src/profile-space/dto/update-profile-space.dto.ts b/nestjs-BE/server/src/profile-space/dto/update-profile-space.dto.ts deleted file mode 100644 index 9bef8027..00000000 --- a/nestjs-BE/server/src/profile-space/dto/update-profile-space.dto.ts +++ /dev/null @@ -1,6 +0,0 @@ -import { PartialType } from '@nestjs/swagger'; -import { CreateProfileSpaceDto } from './create-profile-space.dto'; - -export class UpdateProfileSpaceDto extends PartialType(CreateProfileSpaceDto) { - uuid?: string; -} diff --git a/nestjs-BE/server/src/profile-space/profile-space.controller.ts b/nestjs-BE/server/src/profile-space/profile-space.controller.ts deleted file mode 100644 index 30573c4d..00000000 --- a/nestjs-BE/server/src/profile-space/profile-space.controller.ts +++ /dev/null @@ -1,126 +0,0 @@ -import { - Controller, - Get, - Post, - Body, - Delete, - Param, - Request as Req, - NotFoundException, - HttpException, - HttpStatus, - ConflictException, -} from '@nestjs/common'; -import { ProfileSpaceService } from './profile-space.service'; -import { CreateProfileSpaceDto } from './dto/create-profile-space.dto'; -import { RequestWithUser } from '../utils/interface'; -import { SpacesService } from '../spaces/spaces.service'; -import { ApiTags, ApiResponse, ApiOperation } from '@nestjs/swagger'; -import { ProfilesService } from '../profiles/profiles.service'; - -@Controller('profileSpace') -@ApiTags('profileSpace') -export class ProfileSpaceController { - constructor( - private readonly profileSpaceService: ProfileSpaceService, - private readonly spacesService: SpacesService, - private readonly profilesService: ProfilesService, - ) {} - - @Post('join') - @ApiOperation({ summary: 'Join space' }) - @ApiResponse({ - status: 201, - description: 'Join data has been successfully created.', - }) - @ApiResponse({ - status: 409, - description: 'Conflict. You have already joined the space.', - }) - async create( - @Body() createProfileSpaceDto: CreateProfileSpaceDto, - @Req() req: RequestWithUser, - ) { - const profile = await this.profilesService.findProfile(req.user.uuid); - if (!profile) throw new NotFoundException(); - const profileSpace = await this.profileSpaceService.joinSpace( - profile.uuid, - createProfileSpaceDto.space_uuid, - ); - if (!profileSpace) { - throw new HttpException('Data already exists.', HttpStatus.CONFLICT); - } - return { statusCode: 201, message: 'Created', data: profileSpace }; - } - - @Delete('leave/:space_uuid') - @ApiResponse({ - status: 204, - description: 'Successfully left the space.', - }) - @ApiResponse({ - status: 404, - description: 'Space not found.', - }) - async delete( - @Param('space_uuid') spaceUuid: string, - @Req() req: RequestWithUser, - ) { - const profile = await this.profilesService.findProfile(req.user.uuid); - if (!profile) throw new NotFoundException(); - const space = await this.spacesService.findSpace(spaceUuid); - if (!space) throw new NotFoundException(); - const profileSpace = await this.profileSpaceService.leaveSpace( - profile.uuid, - spaceUuid, - ); - if (!profileSpace) throw new ConflictException(); - const isSpaceEmpty = await this.profileSpaceService.isSpaceEmpty(spaceUuid); - if (isSpaceEmpty) { - await this.spacesService.deleteSpace(spaceUuid); - } - return { statusCode: 204, message: 'No Content' }; - } - - @Get('spaces') - @ApiOperation({ summary: "Get user's spaces" }) - @ApiResponse({ - status: 200, - description: 'Returns a list of spaces.', - }) - async getSpaces(@Req() req: RequestWithUser) { - const profile = await this.profilesService.findProfile(req.user.uuid); - if (!profile) throw new NotFoundException(); - const profileSpaces = - await this.profileSpaceService.findProfileSpacesByProfileUuid( - profile.uuid, - ); - const spaceUuids = profileSpaces.map( - (profileSpace) => profileSpace.spaceUuid, - ); - const spaces = await this.spacesService.findSpaces(spaceUuids); - return { statusCode: 200, message: 'Success', data: spaces }; - } - - @Get('users/:space_uuid') - @ApiOperation({ summary: 'Get users in the space' }) - @ApiResponse({ - status: 200, - description: 'Returns a list of users.', - }) - @ApiResponse({ - status: 404, - description: 'Space not found.', - }) - async getProfiles(@Param('space_uuid') spaceUuid: string) { - const space = await this.spacesService.findSpace(spaceUuid); - if (!space) throw new NotFoundException(); - const profileSpaces = - await this.profileSpaceService.findProfileSpacesBySpaceUuid(space.uuid); - const profileUuids = profileSpaces.map( - (profileSpace) => profileSpace.profileUuid, - ); - const profiles = await this.profilesService.findProfiles(profileUuids); - return { statusCode: 200, message: 'Success', data: profiles }; - } -} diff --git a/nestjs-BE/server/src/profile-space/profile-space.module.ts b/nestjs-BE/server/src/profile-space/profile-space.module.ts index 9e662309..90e8a178 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.module.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.module.ts @@ -1,12 +1,9 @@ -import { Module, forwardRef } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { ProfileSpaceService } from './profile-space.service'; -import { ProfileSpaceController } from './profile-space.controller'; import { ProfilesModule } from '../profiles/profiles.module'; -import { SpacesModule } from '../spaces/spaces.module'; @Module({ - imports: [ProfilesModule, forwardRef(() => SpacesModule)], - controllers: [ProfileSpaceController], + imports: [ProfilesModule], providers: [ProfileSpaceService], exports: [ProfileSpaceService], }) diff --git a/nestjs-BE/server/src/profile-space/profile-space.service.ts b/nestjs-BE/server/src/profile-space/profile-space.service.ts index a1e4769e..383d8988 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.service.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.service.ts @@ -24,22 +24,6 @@ export class ProfileSpaceService { }); } - async findProfileSpacesByProfileUuid( - profileUuid: string, - ): Promise<ProfileSpace[]> { - return this.prisma.profileSpace.findMany({ - where: { profileUuid: profileUuid }, - }); - } - - async findProfileSpacesBySpaceUuid( - spaceUuid: string, - ): Promise<ProfileSpace[]> { - return this.prisma.profileSpace.findMany({ - where: { spaceUuid: spaceUuid }, - }); - } - async findProfileSpaceByBothUuid( profileUuid: string, spaceUuid: string, @@ -66,28 +50,6 @@ export class ProfileSpaceService { } } - async leaveSpace( - profileUuid: string, - spaceUuid: string, - ): Promise<ProfileSpace | null> { - try { - return await this.prisma.profileSpace.delete({ - where: { - spaceUuid_profileUuid: { - spaceUuid: spaceUuid, - profileUuid: profileUuid, - }, - }, - }); - } catch (err) { - if (err instanceof Prisma.PrismaClientKnownRequestError) { - return null; - } else { - throw err; - } - } - } - async isSpaceEmpty(spaceUuid: string) { const first = await this.prisma.profileSpace.findFirst({ where: { diff --git a/nestjs-BE/server/src/spaces/spaces.module.ts b/nestjs-BE/server/src/spaces/spaces.module.ts index 3bd19f1d..8c00d20f 100644 --- a/nestjs-BE/server/src/spaces/spaces.module.ts +++ b/nestjs-BE/server/src/spaces/spaces.module.ts @@ -1,4 +1,4 @@ -import { forwardRef, Module } from '@nestjs/common'; +import { Module } from '@nestjs/common'; import { SpacesService } from './spaces.service'; import { SpacesController } from './spaces.controller'; import { UploadModule } from '../upload/upload.module'; @@ -6,7 +6,7 @@ import { ProfileSpaceModule } from '../profile-space/profile-space.module'; import { UsersModule } from '../users/users.module'; @Module({ - imports: [forwardRef(() => ProfileSpaceModule), UploadModule, UsersModule], + imports: [ProfileSpaceModule, UploadModule, UsersModule], controllers: [SpacesController], providers: [SpacesService], exports: [SpacesService], From 665dc6be40d37fec70735cd52df7ce1a0a7e615a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 17:27:49 +0900 Subject: [PATCH 20/28] =?UTF-8?q?fix:=20uuid=20=ED=95=A8=EC=88=98=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD=EC=97=90=20=EB=94=B0=EB=9D=BC=EC=84=9C=20?= =?UTF-8?q?=ED=8C=A8=ED=84=B4=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/spaces.e2e-spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/server/test/spaces.e2e-spec.ts b/nestjs-BE/server/test/spaces.e2e-spec.ts index 322b28b6..af4bcff5 100644 --- a/nestjs-BE/server/test/spaces.e2e-spec.ts +++ b/nestjs-BE/server/test/spaces.e2e-spec.ts @@ -302,7 +302,7 @@ describe('SpacesController (e2e)', () => { 'S3_BUCKET_NAME', )}\\.s3\\.${configService.get<string>( 'AWS_REGION', - )}\\.amazonaws\\.com\\/[0-9a-f]{32}-`; + )}\\.amazonaws\\.com\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-`; const imageRegExp = new RegExp(imageUrlPattern); return request(app.getHttpServer()) @@ -329,7 +329,7 @@ describe('SpacesController (e2e)', () => { 'S3_BUCKET_NAME', )}\\.s3\\.${configService.get<string>( 'AWS_REGION', - )}\\.amazonaws\\.com\\/[0-9a-f]{32}-`; + )}\\.amazonaws\\.com\\/[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}-`; const imageRegExp = new RegExp(imageUrlPattern); await prisma.profileSpace.create({ data: { spaceUuid: testSpace.uuid, profileUuid: testProfile.uuid }, From 9f8e241479c9c03d720024921911aac0325d744a Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 17:46:19 +0900 Subject: [PATCH 21/28] =?UTF-8?q?test:=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EC=B0=B8=EA=B0=80=20API=20=ED=85=8C=EC=8A=A4?= =?UTF-8?q?=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/spaces.e2e-spec.ts | 114 +++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/nestjs-BE/server/test/spaces.e2e-spec.ts b/nestjs-BE/server/test/spaces.e2e-spec.ts index af4bcff5..b018fa22 100644 --- a/nestjs-BE/server/test/spaces.e2e-spec.ts +++ b/nestjs-BE/server/test/spaces.e2e-spec.ts @@ -470,4 +470,118 @@ describe('SpacesController (e2e)', () => { .expect(HttpStatus.NOT_FOUND) .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); }); + + it('/spaces/:space_uuid/join (POST)', async () => { + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .auth(testToken, { type: 'bearer' }) + .send({ profile_uuid: testProfile.uuid }) + .expect(HttpStatus.CREATED) + .expect({ + message: 'Created', + statusCode: HttpStatus.CREATED, + data: testSpace, + }); + }); + + it('/spaces/:space_uuid/join (POST) profile uuid needed', async () => { + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.BAD_REQUEST) + .expect({ + message: 'Bad Request', + statusCode: HttpStatus.BAD_REQUEST, + }); + }); + + it('/spaces/:space_uuid/join (POST) profile uuid wrong type', async () => { + const number = 1; + + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .auth(testToken, { type: 'bearer' }) + .send({ profile_uuid: number }) + .expect(HttpStatus.BAD_REQUEST) + .expect({ + message: 'Bad Request', + statusCode: HttpStatus.BAD_REQUEST, + }); + }); + + it('/spaces/:space_uuid/join (POST) user not logged in', async () => { + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .send({ profile_uuid: testProfile.uuid }) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ + message: 'Unauthorized', + statusCode: HttpStatus.UNAUTHORIZED, + }); + }); + + it('/spaces/:space_uuid/join (POST) profile user not own', async () => { + const newUser = await prisma.user.create({ data: { uuid: uuid() } }); + const newProfile = await prisma.profile.create({ + data: { + uuid: uuid(), + userUuid: newUser.uuid, + image: 'test image', + nickname: 'test nickname', + }, + }); + + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .auth(testToken, { type: 'bearer' }) + .send({ profile_uuid: newProfile.uuid }) + .expect(HttpStatus.FORBIDDEN) + .expect({ + message: 'Forbidden', + statusCode: HttpStatus.FORBIDDEN, + }); + }); + + it('/spaces/:space_uuid/join (POST) space not exist', async () => { + return request(app.getHttpServer()) + .post(`/spaces/${uuid()}/join`) + .auth(testToken, { type: 'bearer' }) + .send({ profile_uuid: testProfile.uuid }) + .expect(HttpStatus.FORBIDDEN) + .expect({ + message: 'Forbidden', + statusCode: HttpStatus.FORBIDDEN, + }); + }); + + it('/spaces/:space_uuid/join (POST) profile not found', async () => { + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .auth(testToken, { type: 'bearer' }) + .send({ profile_uuid: uuid() }) + .expect(HttpStatus.NOT_FOUND) + .expect({ + message: 'Not Found', + statusCode: HttpStatus.NOT_FOUND, + }); + }); + + it('/spaces/:space_uuid/join (POST) already joined space', async () => { + await prisma.profileSpace.create({ + data: { + spaceUuid: testSpace.uuid, + profileUuid: testProfile.uuid, + }, + }); + + return request(app.getHttpServer()) + .post(`/spaces/${testSpace.uuid}/join`) + .auth(testToken, { type: 'bearer' }) + .send({ profile_uuid: testProfile.uuid }) + .expect(HttpStatus.CONFLICT) + .expect({ + message: 'Conflict', + statusCode: HttpStatus.CONFLICT, + }); + }); }); From 9411967aa70a936c05d4d331bcf669265e02393d Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 18:30:47 +0900 Subject: [PATCH 22/28] =?UTF-8?q?test:=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=20=EB=82=98=EA=B0=80=EA=B8=B0=20API=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/spaces.e2e-spec.ts | 76 ++++++++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/nestjs-BE/server/test/spaces.e2e-spec.ts b/nestjs-BE/server/test/spaces.e2e-spec.ts index b018fa22..92a49cf7 100644 --- a/nestjs-BE/server/test/spaces.e2e-spec.ts +++ b/nestjs-BE/server/test/spaces.e2e-spec.ts @@ -584,4 +584,80 @@ describe('SpacesController (e2e)', () => { statusCode: HttpStatus.CONFLICT, }); }); + + it('/spaces/:space_uuid/profiles/:profile_uuid (DELETE)', async () => { + await prisma.profileSpace.create({ + data: { + profileUuid: testProfile.uuid, + spaceUuid: testSpace.uuid, + }, + }); + + return request(app.getHttpServer()) + .delete(`/spaces/${testSpace.uuid}/profiles/${testProfile.uuid}`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect({ message: 'OK', statusCode: HttpStatus.OK }); + }); + + it('/spaces/:space_uuid/profiles/:profile_uuid (DELETE) user not logged in', async () => { + await prisma.profileSpace.create({ + data: { + profileUuid: testProfile.uuid, + spaceUuid: testSpace.uuid, + }, + }); + + return request(app.getHttpServer()) + .delete(`/spaces/${testSpace.uuid}/profiles/${testProfile.uuid}`) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: HttpStatus.UNAUTHORIZED }); + }); + + it('/spaces/:space_uuid/profiles/:profile_uuid (DELETE) profile user not own', async () => { + const newUser = await prisma.user.create({ data: { uuid: uuid() } }); + const newProfile = await prisma.profile.create({ + data: { + uuid: uuid(), + userUuid: newUser.uuid, + image: 'test image', + nickname: 'test nickname', + }, + }); + await prisma.profileSpace.create({ + data: { + profileUuid: testProfile.uuid, + spaceUuid: testSpace.uuid, + }, + }); + + return request(app.getHttpServer()) + .delete(`/spaces/${testSpace.uuid}/profiles/${newProfile.uuid}`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.FORBIDDEN) + .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); + }); + + it('/spaces/:space_uuid/profiles/:profile_uuid (DELETE) profile user not own', async () => { + await prisma.profileSpace.create({ + data: { + profileUuid: testProfile.uuid, + spaceUuid: testSpace.uuid, + }, + }); + + return request(app.getHttpServer()) + .delete(`/spaces/${testSpace.uuid}/profiles/${uuid()}`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.NOT_FOUND) + .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); + }); + + it('/spaces/:space_uuid/profiles/:profile_uuid (DELETE) profile user not own', async () => { + return request(app.getHttpServer()) + .delete(`/spaces/${testSpace.uuid}/profiles/${testProfile.uuid}`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.NOT_FOUND) + .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); + }); }); From d614c8cd54b202ec92bc7e34090f270f03bb0204 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 18:32:32 +0900 Subject: [PATCH 23/28] =?UTF-8?q?fix:=20=EC=9D=91=EB=8B=B5=20=EC=83=81?= =?UTF-8?q?=ED=83=9C=20=EC=BD=94=EB=93=9C=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit No Content는 본문이 포함되지 않는 것이 표준 --- nestjs-BE/server/src/spaces/spaces.controller.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index cf2a7170..682351db 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -16,7 +16,6 @@ import { Query, BadRequestException, Delete, - HttpCode, } from '@nestjs/common'; import { FileInterceptor } from '@nestjs/platform-express'; import { SpacesService } from './spaces.service'; @@ -231,7 +230,6 @@ export class SpacesController { } @Delete(':space_uuid/profiles/:profile_uuid') - @HttpCode(HttpStatus.NO_CONTENT) @ApiOperation({ summary: 'Leave space' }) @ApiResponse({ status: HttpStatus.NO_CONTENT, @@ -259,7 +257,7 @@ export class SpacesController { @Req() req: RequestWithUser, ) { await this.spacesService.leaveSpace(req.user.uuid, profileUuid, spaceUuid); - return { statusCode: HttpStatus.NO_CONTENT, message: 'No Content' }; + return { statusCode: HttpStatus.OK, message: 'OK' }; } @Get(':space_uuid/profiles') From b9915becf45daebd7900515452b3b6c3b2fc6355 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 18:33:01 +0900 Subject: [PATCH 24/28] =?UTF-8?q?fix:=20API=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/spaces/spaces.controller.ts | 4 ---- 1 file changed, 4 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index 682351db..a8089ca8 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -235,10 +235,6 @@ export class SpacesController { status: HttpStatus.NO_CONTENT, description: 'Successfully left the space.', }) - @ApiResponse({ - status: HttpStatus.BAD_REQUEST, - description: 'Profile uuid needed.', - }) @ApiResponse({ status: HttpStatus.UNAUTHORIZED, description: 'User not logged in.', From ea353ec2ccd6527e87d4dc2a7c00f9d5cbc8cb65 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 19:42:43 +0900 Subject: [PATCH 25/28] =?UTF-8?q?test:=20=EC=8A=A4=ED=8E=98=EC=9D=B4?= =?UTF-8?q?=EC=8A=A4=EC=97=90=20=EC=B0=B8=EA=B0=80=ED=95=9C=20=ED=94=84?= =?UTF-8?q?=EB=A1=9C=ED=95=84=20=EA=B0=80=EC=A0=B8=EC=98=A4=EA=B8=B0=20API?= =?UTF-8?q?=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/test/spaces.e2e-spec.ts | 72 ++++++++++++++++++++++++ 1 file changed, 72 insertions(+) diff --git a/nestjs-BE/server/test/spaces.e2e-spec.ts b/nestjs-BE/server/test/spaces.e2e-spec.ts index 92a49cf7..1a0882b4 100644 --- a/nestjs-BE/server/test/spaces.e2e-spec.ts +++ b/nestjs-BE/server/test/spaces.e2e-spec.ts @@ -660,4 +660,76 @@ describe('SpacesController (e2e)', () => { .expect(HttpStatus.NOT_FOUND) .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); }); + + it('/spaces/:space_uuid/profiles (GET)', async () => { + await prisma.profileSpace.create({ + data: { + profileUuid: testProfile.uuid, + spaceUuid: testSpace.uuid, + }, + }); + + return request(app.getHttpServer()) + .get( + `/spaces/${testSpace.uuid}/profiles?profile_uuid=${testProfile.uuid}`, + ) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.OK) + .expect((res) => { + expect(res.body.message).toBe('OK'); + expect(res.body.statusCode).toBe(HttpStatus.OK); + expect(res.body.data).toEqual(expect.arrayContaining([testProfile])); + }); + }); + + it('/spaces/:space_uuid/profiles (GET) profile uuid needed', async () => { + return request(app.getHttpServer()) + .get(`/spaces/${testSpace.uuid}/profiles`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.BAD_REQUEST) + .expect({ message: 'Bad Request', statusCode: HttpStatus.BAD_REQUEST }); + }); + + it('/spaces/:space_uuid/profiles (GET) user not logged in', async () => { + return request(app.getHttpServer()) + .get(`/spaces/${testSpace.uuid}/profiles`) + .expect(HttpStatus.UNAUTHORIZED) + .expect({ message: 'Unauthorized', statusCode: HttpStatus.UNAUTHORIZED }); + }); + + it('/spaces/:space_uuid/profiles (GET) profile user not own', async () => { + const newUser = await prisma.user.create({ data: { uuid: uuid() } }); + const newProfile = await prisma.profile.create({ + data: { + uuid: uuid(), + userUuid: newUser.uuid, + image: 'test image', + nickname: 'test nickname', + }, + }); + + return request(app.getHttpServer()) + .get(`/spaces/${testSpace.uuid}/profiles?profile_uuid=${newProfile.uuid}`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.FORBIDDEN) + .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); + }); + + it('/spaces/:space_uuid/profiles (GET) profile not joined space', async () => { + return request(app.getHttpServer()) + .get( + `/spaces/${testSpace.uuid}/profiles?profile_uuid=${testProfile.uuid}`, + ) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.FORBIDDEN) + .expect({ message: 'Forbidden', statusCode: HttpStatus.FORBIDDEN }); + }); + + it('/spaces/:space_uuid/profiles (GET) profile not found', async () => { + return request(app.getHttpServer()) + .get(`/spaces/${testSpace.uuid}/profiles?profile_uuid=${uuid()}`) + .auth(testToken, { type: 'bearer' }) + .expect(HttpStatus.NOT_FOUND) + .expect({ message: 'Not Found', statusCode: HttpStatus.NOT_FOUND }); + }); }); From abefa7d86ca9be539d517b4371d6b35dc977188e Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 19:47:24 +0900 Subject: [PATCH 26/28] =?UTF-8?q?fix:=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/spaces/spaces.controller.spec.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts index b7745766..0c096ad9 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.spec.ts @@ -533,8 +533,8 @@ describe('SpacesController', () => { ); await expect(response).resolves.toEqual({ - statusCode: HttpStatus.NO_CONTENT, - message: 'No Content', + statusCode: HttpStatus.OK, + message: 'OK', }); }); From 1b78d61d52af9162253b52e0268a091baaf76908 Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 19:52:37 +0900 Subject: [PATCH 27/28] =?UTF-8?q?fix:=20API=20=EC=84=A4=EB=AA=85=20?= =?UTF-8?q?=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- nestjs-BE/server/src/spaces/spaces.controller.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/nestjs-BE/server/src/spaces/spaces.controller.ts b/nestjs-BE/server/src/spaces/spaces.controller.ts index a8089ca8..03b3b357 100644 --- a/nestjs-BE/server/src/spaces/spaces.controller.ts +++ b/nestjs-BE/server/src/spaces/spaces.controller.ts @@ -273,11 +273,11 @@ export class SpacesController { }) @ApiResponse({ status: HttpStatus.FORBIDDEN, - description: 'Profile user not own.', + description: 'Profile user not own. Profile not joined space.', }) @ApiResponse({ status: HttpStatus.NOT_FOUND, - description: 'Profile not found. Profile not joined space.', + description: 'Profile not found.', }) async findProfilesInSpace( @Param('space_uuid') spaceUuid: string, From 7a7fe07861593eaf065e15a2de470cf6f092190c Mon Sep 17 00:00:00 2001 From: Conut-1 <1mim1@naver.com> Date: Mon, 7 Oct 2024 20:09:22 +0900 Subject: [PATCH 28/28] =?UTF-8?q?fix:=20=ED=94=84=EB=A1=9C=ED=95=84=20?= =?UTF-8?q?=EC=B0=B8=EA=B0=80=20=EC=97=AC=EB=B6=80=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../profile-space.service.spec.ts | 39 ++++++++++++++++++- .../profile-space/profile-space.service.ts | 10 +++++ .../server/src/spaces/spaces.service.spec.ts | 19 +++++++++ nestjs-BE/server/src/spaces/spaces.service.ts | 5 +++ 4 files changed, 72 insertions(+), 1 deletion(-) diff --git a/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts b/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts index df6aefae..40aa3d28 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.service.spec.ts @@ -1,6 +1,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { ProfileSpaceService } from './profile-space.service'; import { PrismaService } from '../prisma/prisma.service'; +import { ProfileSpace } from '@prisma/client'; describe('ProfileSpaceService', () => { let profileSpaceService: ProfileSpaceService; @@ -12,7 +13,12 @@ describe('ProfileSpaceService', () => { ProfileSpaceService, { provide: PrismaService, - useValue: { profileSpace: { findFirst: jest.fn() } }, + useValue: { + profileSpace: { + findFirst: jest.fn(), + findUnique: jest.fn(), + }, + }, }, ], }).compile(); @@ -43,4 +49,35 @@ describe('ProfileSpaceService', () => { await expect(isSpaceEmpty).resolves.toBeFalsy(); }); + + it('isProfileInSpace joined', async () => { + const spaceUuid = 'space uuid'; + const profileUuid = 'profile uuid'; + const profileSpaceMock = { profileUuid, spaceUuid } as ProfileSpace; + + (prisma.profileSpace.findUnique as jest.Mock).mockResolvedValue( + profileSpaceMock, + ); + + const isProfileInSpace = profileSpaceService.isProfileInSpace( + profileUuid, + spaceUuid, + ); + + await expect(isProfileInSpace).resolves.toBeTruthy(); + }); + + it('isProfileInSpace not joined', async () => { + const spaceUuid = 'space uuid'; + const profileUuid = 'profile uuid'; + + (prisma.profileSpace.findUnique as jest.Mock).mockResolvedValue(null); + + const isProfileInSpace = profileSpaceService.isProfileInSpace( + profileUuid, + spaceUuid, + ); + + await expect(isProfileInSpace).resolves.toBeFalsy(); + }); }); diff --git a/nestjs-BE/server/src/profile-space/profile-space.service.ts b/nestjs-BE/server/src/profile-space/profile-space.service.ts index 383d8988..851c6f79 100644 --- a/nestjs-BE/server/src/profile-space/profile-space.service.ts +++ b/nestjs-BE/server/src/profile-space/profile-space.service.ts @@ -58,4 +58,14 @@ export class ProfileSpaceService { }); return first ? false : true; } + + async isProfileInSpace( + profileUuid: string, + spaceUuid: string, + ): Promise<boolean> { + const profileSpace = await this.prisma.profileSpace.findUnique({ + where: { spaceUuid_profileUuid: { spaceUuid, profileUuid } }, + }); + return profileSpace ? true : false; + } } diff --git a/nestjs-BE/server/src/spaces/spaces.service.spec.ts b/nestjs-BE/server/src/spaces/spaces.service.spec.ts index 16355fc3..fa7c1237 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.spec.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.spec.ts @@ -34,6 +34,7 @@ describe('SpacesService', () => { createProfileSpace: jest.fn(), deleteProfileSpace: jest.fn(), isSpaceEmpty: jest.fn(), + isProfileInSpace: jest.fn(), }, }, { @@ -272,4 +273,22 @@ describe('SpacesService', () => { await expect(res).rejects.toThrow(ForbiddenException); }); + + it('findProfilesInSpace profile not joined space', async () => { + const userUuid = 'user uuid'; + const profileUuid = 'profile uuid'; + const spaceUuid = 'space uuid'; + + (profileSpaceService.isProfileInSpace as jest.Mock).mockResolvedValue( + false, + ); + + const res = spacesService.findProfilesInSpace( + userUuid, + profileUuid, + spaceUuid, + ); + + await expect(res).rejects.toThrow(ForbiddenException); + }); }); diff --git a/nestjs-BE/server/src/spaces/spaces.service.ts b/nestjs-BE/server/src/spaces/spaces.service.ts index 26152155..bef2ef57 100644 --- a/nestjs-BE/server/src/spaces/spaces.service.ts +++ b/nestjs-BE/server/src/spaces/spaces.service.ts @@ -117,6 +117,11 @@ export class SpacesService { spaceUuid: string, ): Promise<Profile[]> { await this.usersService.verifyUserProfile(userUuid, profileUuid); + const isProfileInSpace = await this.profileSpaceService.isProfileInSpace( + profileUuid, + spaceUuid, + ); + if (!isProfileInSpace) throw new ForbiddenException(); return this.prisma.profile.findMany({ where: { spaces: { some: { spaceUuid } } }, });