Skip to content

Commit

Permalink
Feat: 북마크 생성, 조회, 삭제 기능
Browse files Browse the repository at this point in the history
- 북마크 생성 시 문제가 이미 들어 있다면 update
- 없으면 insert 하도록 구현
- 북마크 조회 시 해당 유저의 북마크 리스트를 반환
- 북마크 삭제 시 해당 북마크를 삭제
- 북마크 생성, 조회, 삭제 시 유저 정보를 받아오는 pipe 구현
- 북마크 생성 dto 구현

Related to #42
  • Loading branch information
Zamoca42 committed Feb 26, 2024
1 parent b1ad08c commit 7f16772
Show file tree
Hide file tree
Showing 5 changed files with 144 additions and 15 deletions.
45 changes: 31 additions & 14 deletions packages/backend/src/bookmark/bookmark.controller.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import {
Body,
Controller,
Delete,
Get,
Expand All @@ -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')
Expand All @@ -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}.`,
);
}
}
3 changes: 3 additions & 0 deletions packages/backend/src/bookmark/bookmark.module.ts
Original file line number Diff line number Diff line change
@@ -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],
})
Expand Down
50 changes: 49 additions & 1 deletion packages/backend/src/bookmark/bookmark.service.ts
Original file line number Diff line number Diff line change
@@ -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,
},
});
}
}
40 changes: 40 additions & 0 deletions packages/backend/src/bookmark/dto/post-bookmark-body.dto.ts
Original file line number Diff line number Diff line change
@@ -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;
}
21 changes: 21 additions & 0 deletions packages/backend/src/bookmark/pipe/payload.pipe.ts
Original file line number Diff line number Diff line change
@@ -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<AuthToken, Promise<string>>
{
constructor(private readonly authService: AuthService) {}

async transform(value: AuthToken) {
const payload = this.authService.decodeToken(value.accessToken);
return payload.sub;
}
}

0 comments on commit 7f16772

Please sign in to comment.