Skip to content

Commit

Permalink
[NDD-111]: Question API에서 질문을 등록하고, 삭제하는 기능 구현(2/2) (#34)
Browse files Browse the repository at this point in the history
* feat: 질문을 추가하고 id를 통해 삭제하는 기능 구현

* fix: 누락된 코드 추가

* style: lint 적용

* refactor: auth api에도 /api/auth로 경로 명세 통일

* feat: 커스텀 질문을 저장하는 비즈니스 로직 구현

* feat: 컨트롤러에서 커스텀 질문을 저장하는 로직에 대한 리펙토링 진행 && 비즈니스 로직에서 일반 질문 저장 로직 주석처리

* docs: 게시물 생성에 대한 swagger 문서 추가

* feat: 게시글 삭제 로직 구현

* feat: 다대다 매핑의 Question, Member에서 Question을 삭제하는 비즈니스 로직 구현

* feat: 컨트롤러 로직에서 Bearer토큰을 이용한 AuthGuard가 적용되게 기능 추가

* style: lint 적용

* refactor: 카테고리 별 조회를 위한 쿼리문 수정
  • Loading branch information
JangAJang authored Nov 13, 2023
1 parent 2d1965c commit 66d6cd6
Show file tree
Hide file tree
Showing 6 changed files with 123 additions and 15 deletions.
3 changes: 1 addition & 2 deletions BE/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -15,12 +15,11 @@ module.exports = {
node: true,
jest: true,
},
ignorePatterns: ['.eslintrc.js'],
ignorePatterns: ['/eslintrc.js'],
rules: {
'@typescript-eslint/interface-name-prefix': 'off',
'@typescript-eslint/explicit-function-return-type': 'off',
'@typescript-eslint/explicit-module-boundary-types': 'off',
'@typescript-eslint/no-explicit-any': 'off',
'prettier/prettier': ['error', { endOfLine: 'auto' }],
},
};
53 changes: 49 additions & 4 deletions BE/src/question/controller/question.controller.ts
Original file line number Diff line number Diff line change
@@ -1,20 +1,52 @@
import { Controller, Get, Param, Req } from '@nestjs/common';
import { ApiResponse, ApiTags } from '@nestjs/swagger';
import {
Body,
Controller,
Delete,
Get,
Param,
Post, Query,
Req,
UseGuards,
} from '@nestjs/common';
import {
ApiBearerAuth,
ApiOperation,
ApiResponse,
ApiTags,
} from '@nestjs/swagger';
import { QuestionService } from '../service/question.service';
import { Request } from 'express';
import { getTokenValue } from 'src/util/token.util';
import { TokenService } from 'src/token/service/token.service';
import { QuestionListResponse } from '../dto/questionListResponse';
import { createApiResponseOption } from '../../util/swagger.util';
import { AuthGuard } from '@nestjs/passport';
import { Member } from '../../member/entity/member';
import { CustomQuestionRequest } from '../dto/customQuestionRequest';

@Controller('api/question')
@Controller('/api/question')
@ApiTags('question')
export class QuestionController {
constructor(
private questionService: QuestionService,
private tokenService: TokenService,
) {}

@Post('')
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
@ApiOperation({ summary: '커스텀 질문 생성' })
@ApiResponse(createApiResponseOption(201, '커스텀 질문 생성', null))
async createCustomQuestion(
@Req() req: Request,
@Body() customQuestionRequest: CustomQuestionRequest,
) {
await this.questionService.createCustomQuestion(
customQuestionRequest,
req.user as Member,
);
}

@Get('')
@ApiResponse(
createApiResponseOption(
Expand All @@ -23,8 +55,9 @@ export class QuestionController {
QuestionListResponse,
),
)
@ApiOperation({ summary: '카테고리별 질문 조회' })
async findAllByCategory(
@Param('category') category: string,
@Query('category') category: string,
@Req() request: Request,
): Promise<any> {
return await this.questionService.findByCategory(
Expand All @@ -33,6 +66,18 @@ export class QuestionController {
);
}

@ApiOperation({ summary: '게시글 삭제 api' })
@UseGuards(AuthGuard('jwt'))
@ApiBearerAuth()
@Delete(':questionId')
@ApiResponse(createApiResponseOption(204, '게시글 삭제', null))
async deleteQuestion(
@Param('questionId') questionId: number,
@Req() req: Request,
) {
await this.questionService.deleteById(questionId, req.user as Member);
}

private async findMember(request: Request) {
try {
const token = getTokenValue(request);
Expand Down
13 changes: 13 additions & 0 deletions BE/src/question/dto/customQuestionRequest.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import { IsNotEmpty, IsString } from '@nestjs/class-validator';
import { ApiProperty } from '@nestjs/swagger';

export class CustomQuestionRequest {
@ApiProperty({
example: '개발자가 되기 좋은 역량은 무엇일까요?',
description: 'content',
required: true,
})
@IsString()
@IsNotEmpty()
readonly content: string;
}
9 changes: 9 additions & 0 deletions BE/src/question/exception/question.exception.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { HttpException } from '@nestjs/common';

class QuestionNotFoundException extends HttpException {
constructor() {
super('해당 커스텀 질문이 존재하지 않습니다.', 404);
}
}

export { QuestionNotFoundException };
26 changes: 21 additions & 5 deletions BE/src/question/repository/question.repository.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,20 +19,36 @@ export class QuestionRepository {
memberId: number,
): Promise<Question[]> {
return (await this.constructQueryBuilder(category, memberId))
.orderBy('question.createdAt', 'DESC')
.orderBy('Question.createdAt', 'DESC')
.getMany();
}

async findById(id: number) {
return await this.questionRepository.findOneBy({ id: id });
}

async findQuestionByIdAndMember_Id(questionId: number, memberId: number) {
return this.questionRepository.findOneBy({
members: { id: memberId },
id: questionId,
});
}

async remove(question: Question) {
await this.questionRepository.remove(question);
}

private async constructQueryBuilder(category: string, memberId: number) {
const queryBuilder = this.questionRepository.createQueryBuilder('question');
const queryBuilder = this.questionRepository.createQueryBuilder('Question');

if (category === 'CUSTOM') {
return queryBuilder
.leftJoin('question.members', 'member')
.where('question.category = :category', { category })
.leftJoin('Question.id', 'QuestionMember')
.leftJoin('QuestionMember.memberId', 'Member')
.where('Question.category = :category', { category })
.andWhere('member.id = :memberId', { memberId });
}

return queryBuilder.where('question.category = :category', { category });
return queryBuilder.where('Question.category = :category', { category });
}
}
34 changes: 30 additions & 4 deletions BE/src/question/service/question.service.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Injectable } from '@nestjs/common';
import { Injectable, UnauthorizedException } from '@nestjs/common';
import { QuestionRepository } from '../repository/question.repository';
import { CreateQuestionRequest } from '../dto/createQuestionRequest';
import { Member } from 'src/member/entity/member';
Expand All @@ -7,6 +7,7 @@ import { isEmpty } from 'class-validator';
import { isCategoryCustom } from '../util/question.util';
import { MemberRepository } from '../../member/repository/member.repository';
import { QuestionListResponse } from '../dto/questionListResponse';
import { CustomQuestionRequest } from '../dto/customQuestionRequest';

@Injectable()
export class QuestionService {
Expand All @@ -15,11 +16,23 @@ export class QuestionService {
private memberRepository: MemberRepository,
) {}

async createQuestion(
createQuestionRequest: CreateQuestionRequest,
// async createQuestion(
// createQuestionRequest: CreateQuestionRequest,
// member: Member,
// ) {
// const question = Question.from(createQuestionRequest, member);
// await this.questionRepository.save(question);
// }

async createCustomQuestion(
customQuestionRequest: CustomQuestionRequest,
member: Member,
) {
const question = Question.from(createQuestionRequest, member);
const questionRequest = {
category: 'CUSTOM',
content: customQuestionRequest.content,
} as CreateQuestionRequest;
const question = Question.from(questionRequest, member);
await this.questionRepository.save(question);
}

Expand All @@ -37,4 +50,17 @@ export class QuestionService {

return QuestionListResponse.from(questionList);
}

async deleteById(id: number, member: Member) {
const question = await this.questionRepository.findQuestionByIdAndMember_Id(
id,
member.id,
);

if (isEmpty(question)) {
throw new UnauthorizedException();
}

await this.questionRepository.remove(question);
}
}

0 comments on commit 66d6cd6

Please sign in to comment.