From 7f16772996bf3f9aa6b8ae157cfffbaa39f44e2e Mon Sep 17 00:00:00 2001 From: Chooooooo Date: Mon, 26 Feb 2024 15:59:38 +0900 Subject: [PATCH] =?UTF-8?q?Feat:=20=EB=B6=81=EB=A7=88=ED=81=AC=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1,=20=EC=A1=B0=ED=9A=8C,=20=EC=82=AD=EC=A0=9C=20?= =?UTF-8?q?=EA=B8=B0=EB=8A=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 북마크 생성 시 문제가 이미 들어 있다면 update - 없으면 insert 하도록 구현 - 북마크 조회 시 해당 유저의 북마크 리스트를 반환 - 북마크 삭제 시 해당 북마크를 삭제 - 북마크 생성, 조회, 삭제 시 유저 정보를 받아오는 pipe 구현 - 북마크 생성 dto 구현 Related to #42 --- .../src/bookmark/bookmark.controller.ts | 45 +++++++++++------ .../backend/src/bookmark/bookmark.module.ts | 3 ++ .../backend/src/bookmark/bookmark.service.ts | 50 ++++++++++++++++++- .../bookmark/dto/post-bookmark-body.dto.ts | 40 +++++++++++++++ .../backend/src/bookmark/pipe/payload.pipe.ts | 21 ++++++++ 5 files changed, 144 insertions(+), 15 deletions(-) create mode 100644 packages/backend/src/bookmark/dto/post-bookmark-body.dto.ts create mode 100644 packages/backend/src/bookmark/pipe/payload.pipe.ts diff --git a/packages/backend/src/bookmark/bookmark.controller.ts b/packages/backend/src/bookmark/bookmark.controller.ts index 97f14f2..0486ac5 100644 --- a/packages/backend/src/bookmark/bookmark.controller.ts +++ b/packages/backend/src/bookmark/bookmark.controller.ts @@ -1,4 +1,5 @@ import { + Body, Controller, Delete, Get, @@ -11,7 +12,8 @@ import { ApiTags } from '@nestjs/swagger'; import { ResponseEntity } from '../common/entity/response.entity'; import { ApiSwagger } from '../common/swagger/api.decorator'; import { ReqToken } from '../auth/decorator/token.decorator'; -import { AuthToken } from '../auth/dto/req-auth-token.dto'; +import { PostBookmarkReqBody } from './dto/post-bookmark-body.dto'; +import { ParseUserIdPipe } from './pipe/payload.pipe'; @ApiTags('문제 북마크') @Controller('bookmark') @@ -20,25 +22,40 @@ export class BookmarkController { @Get() @ApiSwagger({ name: '북마크 조회' }) - async findAll() { - // return ResponseEntity.OK_WITH( - // `Successfully find ${result.length} bookmarks`, - // result, - // ); + async findAllBookmark( + @ReqToken(ParseUserIdPipe, ParseUUIDPipe) userId: string, + ) { + const result = await this.bookmarkService.findAllBookmark(userId); + return ResponseEntity.OK_WITH( + `Successfully find ${result.length} bookmarks`, + result, + ); } @Post() - @ApiSwagger({ name: '북마크 생성', success: 201 }) - async create() { - return ResponseEntity.CREATED('Successfully create bookmark'); + @ApiSwagger({ name: '북마크 생성' }) + async upsertBookmark( + @ReqToken(ParseUserIdPipe, ParseUUIDPipe) userId: string, + @Body() request: PostBookmarkReqBody, + ) { + request.bookmarks.map(async (bookmark) => { + await this.bookmarkService.upsertBookmark(userId, bookmark); + }); + return ResponseEntity.CREATED(`Successfully create bookmarks`); } @Delete('/:uuid') - @ApiSwagger({ name: '토익 문제 수정' }) - deleteOne( - @Param('uuid', ParseUUIDPipe) uuid: string, - @ReqToken() token: AuthToken, + @ApiSwagger({ name: '북마크 삭제' }) + async deleteBookmark( + @Param('uuid', ParseUUIDPipe) questionId: string, + @ReqToken(ParseUserIdPipe, ParseUUIDPipe) userId: string, ) { - return ResponseEntity.OK(`Successfully delete bookmark id: ${uuid}.`); + const result = await this.bookmarkService.deleteBookmark( + userId, + questionId, + ); + return ResponseEntity.OK( + `Successfully delete ${result.count} bookmark id: ${questionId}.`, + ); } } diff --git a/packages/backend/src/bookmark/bookmark.module.ts b/packages/backend/src/bookmark/bookmark.module.ts index 3c703e1..41cd23f 100644 --- a/packages/backend/src/bookmark/bookmark.module.ts +++ b/packages/backend/src/bookmark/bookmark.module.ts @@ -1,8 +1,11 @@ import { Module } from '@nestjs/common'; import { BookmarkService } from './bookmark.service'; import { BookmarkController } from './bookmark.controller'; +import { AuthModule } from '../auth/auth.module'; +import { PrismaModule } from '../prisma/prisma.module'; @Module({ + imports: [AuthModule, PrismaModule], controllers: [BookmarkController], providers: [BookmarkService], }) diff --git a/packages/backend/src/bookmark/bookmark.service.ts b/packages/backend/src/bookmark/bookmark.service.ts index 6547491..a5a3e54 100644 --- a/packages/backend/src/bookmark/bookmark.service.ts +++ b/packages/backend/src/bookmark/bookmark.service.ts @@ -1,4 +1,52 @@ import { Injectable } from '@nestjs/common'; +import { PrismaService } from '../prisma/prisma.service'; +import { BookmarkWithHistory } from './dto/post-bookmark-body.dto'; @Injectable() -export class BookmarkService {} +export class BookmarkService { + constructor(private prisma: PrismaService) {} + + async upsertBookmark(userId: string, bookmark: BookmarkWithHistory) { + const where = { + user_id: userId, + bookmark_question_id: bookmark.question_id, + }; + const Bookmarked = await this.prisma.bookmark.findFirst({ + select: { id: true }, + where, + }); + + if (!Bookmarked) { + return this.prisma.bookmark.create({ + data: { + history: bookmark.history, + ...where, + }, + }); + } else { + return this.prisma.bookmark.update({ + where: { id: Bookmarked.id }, + data: { + history: bookmark.history, + }, + }); + } + } + + findAllBookmark(userId: string) { + return this.prisma.bookmark.findMany({ + where: { + user_id: userId, + }, + }); + } + + deleteBookmark(userId: string, questionId: string) { + return this.prisma.bookmark.deleteMany({ + where: { + user_id: userId, + bookmark_question_id: questionId, + }, + }); + } +} diff --git a/packages/backend/src/bookmark/dto/post-bookmark-body.dto.ts b/packages/backend/src/bookmark/dto/post-bookmark-body.dto.ts new file mode 100644 index 0000000..6d4f4e3 --- /dev/null +++ b/packages/backend/src/bookmark/dto/post-bookmark-body.dto.ts @@ -0,0 +1,40 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { Type } from 'class-transformer'; +import { + IsArray, + IsNotEmpty, + IsOptional, + IsString, + IsUUID, + ValidateNested, +} from 'class-validator'; + +export class PostBookmarkReqBody { + @ApiProperty({ + description: '북마크할 문제의 id와 히스토리', + isArray: true, + type: () => BookmarkWithHistory, + }) + @IsArray() + @ValidateNested({ each: true }) + @Type(() => BookmarkWithHistory) + bookmarks: BookmarkWithHistory[]; +} + +export class BookmarkWithHistory { + @ApiProperty({ + description: '북마크할 문제의 id', + }) + @IsUUID() + @IsNotEmpty() + question_id: string; + + @ApiProperty({ + description: '북마크 틀린문제 히스토리', + required: false, + default: '', + }) + @IsString() + @IsOptional() + history: string; +} diff --git a/packages/backend/src/bookmark/pipe/payload.pipe.ts b/packages/backend/src/bookmark/pipe/payload.pipe.ts new file mode 100644 index 0000000..0d6a4a8 --- /dev/null +++ b/packages/backend/src/bookmark/pipe/payload.pipe.ts @@ -0,0 +1,21 @@ +import { Injectable, PipeTransform } from '@nestjs/common'; +import { AuthToken } from '../../auth/dto/req-auth-token.dto'; +import { AuthService } from '../../auth/auth.service'; + +/** + * Validator supports service container in the case if want to inject dependencies into your custom validator constraint classes + * @see also https://github.com/leosuncin/nest-api-example/blob/master/src/blog/pipes/article.pipe.ts + * @see also https://github.com/typestack/class-validator?tab=readme-ov-file#using-service-container + * @see also https://docs.nestjs.com/techniques/validation + */ +@Injectable() +export class ParseUserIdPipe + implements PipeTransform> +{ + constructor(private readonly authService: AuthService) {} + + async transform(value: AuthToken) { + const payload = this.authService.decodeToken(value.accessToken); + return payload.sub; + } +}