From 605afcf2f311db836824efef529c9da15ecbdfcd Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Wed, 15 Jan 2025 21:07:48 +0900 Subject: [PATCH 01/13] =?UTF-8?q?feat::=20=EA=B2=80=EC=83=89=20=EA=B8=B0?= =?UTF-8?q?=EB=B3=B8=EC=A0=81=EC=9D=B8=20=EC=A0=84=EB=9E=B5=20=EC=83=9D?= =?UTF-8?q?=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - category에 따라 전공, 교양, 학문의 기초가 되게 --- .../strategy/academic-foundation-search-strategy.ts | 9 +++++++++ src/course/strategy/general-search-strategy.ts | 9 +++++++++ src/course/strategy/major-search-strategy.ts | 9 +++++++++ src/course/strategy/search-strategy.ts | 3 +++ 4 files changed, 30 insertions(+) create mode 100644 src/course/strategy/academic-foundation-search-strategy.ts create mode 100644 src/course/strategy/general-search-strategy.ts create mode 100644 src/course/strategy/major-search-strategy.ts create mode 100644 src/course/strategy/search-strategy.ts diff --git a/src/course/strategy/academic-foundation-search-strategy.ts b/src/course/strategy/academic-foundation-search-strategy.ts new file mode 100644 index 0000000..89623af --- /dev/null +++ b/src/course/strategy/academic-foundation-search-strategy.ts @@ -0,0 +1,9 @@ +import { SearchStrategy } from './search-strategy'; + +const ACADEMIC_FOUNDATION = 'Academic Foundations'; + +export class AcademicFoundationSearchStrategy implements SearchStrategy { + supports(category: string): boolean { + return category === ACADEMIC_FOUNDATION; + } +} diff --git a/src/course/strategy/general-search-strategy.ts b/src/course/strategy/general-search-strategy.ts new file mode 100644 index 0000000..6073cb1 --- /dev/null +++ b/src/course/strategy/general-search-strategy.ts @@ -0,0 +1,9 @@ +import { SearchStrategy } from './search-strategy'; + +const GENERAL = 'General Studies'; + +export class GeneralSearchStrategy implements SearchStrategy { + supports(category: string): boolean { + return category === GENERAL; + } +} diff --git a/src/course/strategy/major-search-strategy.ts b/src/course/strategy/major-search-strategy.ts new file mode 100644 index 0000000..e983f57 --- /dev/null +++ b/src/course/strategy/major-search-strategy.ts @@ -0,0 +1,9 @@ +import { SearchStrategy } from './search-strategy'; + +const MAJOR = 'Major'; + +export class MajorSearchStrategy implements SearchStrategy { + supports(category: string): boolean { + return category === MAJOR; + } +} diff --git a/src/course/strategy/search-strategy.ts b/src/course/strategy/search-strategy.ts new file mode 100644 index 0000000..d254910 --- /dev/null +++ b/src/course/strategy/search-strategy.ts @@ -0,0 +1,3 @@ +export interface SearchStrategy { + supports(category: string): boolean; +} From 907daf3dd002327adda6bf2f2aa76f8047ea33e0 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:54:11 +0900 Subject: [PATCH 02/13] =?UTF-8?q?feat::=20=EA=B0=95=EC=9D=98=20=EC=B9=B4?= =?UTF-8?q?=ED=85=8C=EA=B3=A0=EB=A6=AC=20enum=20=EC=83=9D=EC=84=B1=20?= =?UTF-8?q?=EB=B0=8F=20=EC=BB=A4=EC=8A=A4=ED=85=80=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enums/course-category.enum.ts | 6 ++++++ src/utils/exception.util.ts | 6 ++++++ 2 files changed, 12 insertions(+) create mode 100644 src/enums/course-category.enum.ts diff --git a/src/enums/course-category.enum.ts b/src/enums/course-category.enum.ts new file mode 100644 index 0000000..4199f8e --- /dev/null +++ b/src/enums/course-category.enum.ts @@ -0,0 +1,6 @@ +export enum CourseCategory { + ALL_CLASS = 'All Class', + MAJOR = 'Major', + GENERAL_STUDIES = 'General Studies', + ACADEMIC_FOUNDATIONS = 'Academic Foundations', +} diff --git a/src/utils/exception.util.ts b/src/utils/exception.util.ts index c268328..ddecb4c 100644 --- a/src/utils/exception.util.ts +++ b/src/utils/exception.util.ts @@ -309,6 +309,12 @@ export const kukeyExceptions = createKukeyExceptions({ errorCode: 3004, statusCode: 409, }, + COURSE_SEARCH_STRATEGY_NOT_FOUND: { + name: 'COURSE_SEARCH_STRATEGY_NOT_FOUND', + message: 'Course search strategy not found.', + errorCode: 3005, + statusCode: 404, + }, // - 31xx : Schedule INVALID_TIME_RANGE: { name: 'INVALID_TIME_RANGE', From a564651cb03afe43355b4c81096953b521ebd747 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Tue, 21 Jan 2025 19:57:05 +0900 Subject: [PATCH 03/13] =?UTF-8?q?feat::=20=EA=B0=95=EC=9D=98=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EB=A6=AC=ED=8C=A9=ED=86=A0=EB=A7=81=EC=97=90=20?= =?UTF-8?q?=EB=94=B0=EB=A5=B8=20=EC=83=88=EB=A1=9C=EC=9A=B4=20DTO=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/dto/search-course-new.dto.ts | 44 +++++++++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 src/course/dto/search-course-new.dto.ts diff --git a/src/course/dto/search-course-new.dto.ts b/src/course/dto/search-course-new.dto.ts new file mode 100644 index 0000000..120a93f --- /dev/null +++ b/src/course/dto/search-course-new.dto.ts @@ -0,0 +1,44 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsEnum, IsInt, IsOptional, IsString, Length } from 'class-validator'; +import { CourseCategory } from 'src/enums/course-category.enum'; + +export class SearchCourseNewDto { + @ApiPropertyOptional({ + description: '커서 id, 값이 존재하지 않으면 첫 페이지', + }) + @IsInt() + @IsOptional() + cursorId?: number; + + @ApiProperty({ description: '연도' }) + @IsString() + @Length(4) + year: string; + + @ApiProperty({ description: '학기' }) + @IsString() + @Length(1) + semester: string; + + @ApiProperty({ + description: '강의 카테고리 (모든 강의, 전공, 교양, 학문의 기초)', + enum: CourseCategory, + }) + @IsEnum(CourseCategory) + category: CourseCategory; + + @ApiPropertyOptional({ + description: '검색 키워드 (강의명, 교수명, 학수번호)', + }) + @Length(2) + @IsOptional() + keyword?: string; + + @ApiPropertyOptional({ + description: + 'Major일때 major를, Academic Foundation일 때 college를 넣어주세요.', + }) + @IsString() + @IsOptional() + classification?: string; +} From 76881fcb94a8cdc13c233ac0f47da04aa426382d Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:45:58 +0900 Subject: [PATCH 04/13] =?UTF-8?q?refactor::=20Course=20=EC=BB=A8=ED=8A=B8?= =?UTF-8?q?=EB=A1=A4=EB=9F=AC=20=EC=88=98=EC=A0=95=20=EB=B0=8F=20=EB=8D=B0?= =?UTF-8?q?=EC=BD=94=EB=A0=88=EC=9D=B4=ED=84=B0=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/course.controller.ts | 148 +-------------- src/decorators/docs/course.decorator.ts | 240 +----------------------- 2 files changed, 16 insertions(+), 372 deletions(-) diff --git a/src/course/course.controller.ts b/src/course/course.controller.ts index a5bd483..09d3ff3 100644 --- a/src/course/course.controller.ts +++ b/src/course/course.controller.ts @@ -1,17 +1,10 @@ -import { Controller, Get, Param, Query, UseGuards } from '@nestjs/common'; +import { Controller, Get, Query, UseGuards } from '@nestjs/common'; import { CourseService } from './course.service'; import { ApiTags } from '@nestjs/swagger'; -import { CommonCourseResponseDto } from './dto/common-course-response.dto'; import { JwtAuthGuard } from 'src/auth/guards/jwt-auth.guard'; -import { SearchCourseCodeDto } from './dto/search-course-code.dto'; -import { SearchCourseNameDto } from './dto/search-course-name.dto'; -import { SearchProfessorNameDto } from './dto/search-professor-name.dto'; import { PaginatedCoursesDto } from './dto/paginated-courses.dto'; import { CourseDocs } from 'src/decorators/docs/course.decorator'; -import { GetGeneralCourseDto } from './dto/get-general-course.dto'; -import { GetMajorCourseDto } from './dto/get-major-course.dto'; -import { GetAcademicFoundationCourseDto } from './dto/get-academic-foundation-course.dto'; -import { SearchCoursesWithKeywordDto } from './dto/search-courses-with-keyword.dto'; +import { SearchCourseNewDto } from './dto/search-course-new.dto'; @ApiTags('course') @CourseDocs @@ -20,139 +13,10 @@ export class CourseController { constructor(private courseService: CourseService) {} @UseGuards(JwtAuthGuard) - @Get('search-all') - async searchAllCourses( - @Query() searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, + @Get() + async searchCourses( + @Query() searchCourseNewDto: SearchCourseNewDto, ): Promise { - return await this.courseService.searchAllCourses( - searchCoursesWithKeywordDto, - ); - } - - @UseGuards(JwtAuthGuard) - @Get('search-major') - async searchMajorCourses( - @Query('major') major: string, - @Query() searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - return await this.courseService.searchMajorCourses( - major, - searchCoursesWithKeywordDto, - ); - } - - @UseGuards(JwtAuthGuard) - @Get('search-general') - async searchGeneralCourses( - @Query() searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - return await this.courseService.searchGeneralCourses( - searchCoursesWithKeywordDto, - ); - } - - @UseGuards(JwtAuthGuard) - @Get('search-academic-foundation') - async searchAcademicFoundationCourses( - @Query('college') college: string, - @Query() searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - return await this.courseService.searchAcademicFoundationCourses( - college, - searchCoursesWithKeywordDto, - ); - } - - // 학수번호 검색 - @UseGuards(JwtAuthGuard) - @Get('search-course-code') - async searchCourseCode( - @Query() searchCourseCodeDto: SearchCourseCodeDto, - ): Promise { - return await this.courseService.searchCourseCode(searchCourseCodeDto); - } - - // 전공 -- 과목명 검색 - @UseGuards(JwtAuthGuard) - @Get('search-major-course-name') - async searchMajorCourseName( - @Query('major') major: string, - @Query() searchCourseNameDto: SearchCourseNameDto, - ): Promise { - return await this.courseService.searchMajorCourseName( - major, - searchCourseNameDto, - ); - } - - // 교양 - 과목명 검색 - @UseGuards(JwtAuthGuard) - @Get('search-general-course-name') - async searchGeneralCourseName( - @Query() searchCourseNameDto: SearchCourseNameDto, - ): Promise { - return await this.courseService.searchGeneralCourseName( - searchCourseNameDto, - ); - } - - // 전공 - 교수님 성함 검색 - @UseGuards(JwtAuthGuard) - @Get('search-major-professor-name') - async searchMajorProfessorName( - @Query('major') major: string, - @Query() searchProfessorNameDto: SearchProfessorNameDto, - ): Promise { - return await this.courseService.searchMajorProfessorName( - major, - searchProfessorNameDto, - ); - } - - // 교양 - 교수님 성함 검색 - @UseGuards(JwtAuthGuard) - @Get('search-general-professor-name') - async searchGeneralProfessorName( - @Query() searchProfessorNameDto: SearchProfessorNameDto, - ): Promise { - return await this.courseService.searchGeneralProfessorName( - searchProfessorNameDto, - ); - } - - // 교양 리스트 - @UseGuards(JwtAuthGuard) - @Get('general') - async getGeneralCourses( - @Query() getGeneralCourseDto: GetGeneralCourseDto, - ): Promise { - return await this.courseService.getGeneralCourses(getGeneralCourseDto); - } - - // 전공 리스트 (학부별) - @UseGuards(JwtAuthGuard) - @Get('major') - async getMajorCourses( - @Query() getMajorCourseDto: GetMajorCourseDto, - ): Promise { - return await this.courseService.getMajorCourses(getMajorCourseDto); - } - - // 학문의 기초 리스트 - @UseGuards(JwtAuthGuard) - @Get('academic-foundation') - async getAcademicFoundationCourses( - @Query() getAcademicFoundationCourseDto: GetAcademicFoundationCourseDto, - ): Promise { - return await this.courseService.getAcademicFoundationCourses( - getAcademicFoundationCourseDto, - ); - } - - @Get('/:courseId') - async getCourse( - @Param('courseId') courseId: number, - ): Promise { - return await this.courseService.getCourse(courseId); + return await this.courseService.searchCourses(searchCourseNewDto); } } diff --git a/src/decorators/docs/course.decorator.ts b/src/decorators/docs/course.decorator.ts index 50d1588..49c335e 100644 --- a/src/decorators/docs/course.decorator.ts +++ b/src/decorators/docs/course.decorator.ts @@ -1,281 +1,61 @@ import { ApiBearerAuth, ApiOperation, - ApiParam, ApiQuery, ApiResponse, } from '@nestjs/swagger'; import { MethodNames } from 'src/common/types/method'; import { CourseController } from 'src/course/course.controller'; -import { CommonCourseResponseDto } from 'src/course/dto/common-course-response.dto'; import { PaginatedCoursesDto } from 'src/course/dto/paginated-courses.dto'; import { ApiKukeyExceptionResponse } from '../api-kukey-exception-response'; type CourseEndPoints = MethodNames; const CourseDocsMap: Record = { - searchAllCourses: [ + searchCourses: [ ApiBearerAuth('accessToken'), ApiOperation({ - summary: 'keyword로 전체 강의 검색', - description: 'keyword를 입력하여 전체 강의에서 검색합니다.', - }), - ApiResponse({ - status: 200, - description: 'keyword로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ], - searchMajorCourses: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: 'keyword로 전공 강의 검색', - description: 'keyword를 입력하여 전공 강의에서 검색합니다.', - }), - ApiQuery({ - name: 'major', - required: true, - type: 'string', - }), - ApiResponse({ - status: 200, - description: 'keyword로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ApiKukeyExceptionResponse(['MAJOR_REQUIRED']), - ], - searchGeneralCourses: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: 'keyword로 교양 강의 검색', - description: 'keyword를 입력하여 교양 강의에서 검색합니다.', - }), - ApiResponse({ - status: 200, - description: 'keyword로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ], - searchAcademicFoundationCourses: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: 'keyword로 학문의 기초 강의 검색', - description: - 'keyword를 입력하여 단과대 별 학문의 기초 강의에서 검색합니다.', - }), - ApiQuery({ - name: 'college', - required: true, - type: 'string', - }), - ApiResponse({ - status: 200, - description: 'keyword로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ApiKukeyExceptionResponse(['COLLEGE_REQUIRED']), - ], - searchCourseCode: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '학수번호로 강의 검색', - description: '학수번호를 입력하여 강의를 검색합니다.', - }), - ApiQuery({ - name: 'courseCode', - required: true, - type: 'string', + summary: '강의 검색', + description: '하나의 엔드포인트로 모든 강의검색 로직을 통합했습니다.', }), ApiQuery({ name: 'cursorId', required: false, type: 'number', }), - ApiResponse({ - status: 200, - description: '학수번호로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ], - searchMajorCourseName: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '전공 과목명 강의 검색', - description: '전공 과목명을 입력하여 강의를 검색합니다.', - }), ApiQuery({ - name: 'major', + name: 'year', required: true, type: 'string', }), ApiQuery({ - name: 'courseName', + name: 'semester', required: true, type: 'string', }), ApiQuery({ - name: 'cursorId', - required: false, - type: 'number', - }), - ApiResponse({ - status: 200, - description: '전공 과목명으로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ApiKukeyExceptionResponse(['MAJOR_REQUIRED']), - ], - searchGeneralCourseName: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '교양 과목명 강의 검색', - description: '교양 과목명을 입력하여 강의를 검색합니다.', - }), - ApiQuery({ - name: 'courseName', + name: 'category', required: true, - type: 'string', + type: 'enum', }), ApiQuery({ - name: 'cursorId', + name: 'keyword', required: false, - type: 'number', - }), - ApiResponse({ - status: 200, - description: '교양 과목명으로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ], - searchMajorProfessorName: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '전공 과목 담당 교수님 성함으로 강의 검색', - description: '전공 과목 담당 교수님 성함을 입력하여 강의를 검색합니다.', - }), - ApiQuery({ - name: 'major', - required: true, - type: 'string', - }), - ApiQuery({ - name: 'professorName', - required: true, type: 'string', }), ApiQuery({ - name: 'cursorId', + name: 'classification', required: false, - type: 'number', - }), - ApiResponse({ - status: 200, - description: '전공 과목 담당 교수님 성함으로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ApiKukeyExceptionResponse(['MAJOR_REQUIRED']), - ], - searchGeneralProfessorName: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '교양 담당 교수님 성함으로 강의 검색', - description: '교양 담당 교수님 성함을 입력하여 강의를 검색합니다.', - }), - ApiQuery({ - name: 'professorName', - required: true, type: 'string', }), - ApiQuery({ - name: 'cursorId', - required: false, - type: 'number', - }), - ApiResponse({ - status: 200, - description: '교양 담당 교수님 성함으로 강의 검색 성공 시', - type: PaginatedCoursesDto, - }), - ], - getGeneralCourses: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '교양 강의 조회', - description: '모든 교양 강의를 조회합니다.', - }), - ApiQuery({ - name: 'cursorId', - required: false, - type: 'number', - }), ApiResponse({ status: 200, - description: '교양 강의 조회 성공 시', - type: PaginatedCoursesDto, - }), - ], - getMajorCourses: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '전공 강의 조회', - description: '해당 과의 모든 전공 강의를 조회합니다.', - }), - ApiQuery({ - name: 'major', - required: true, - type: 'string', - }), - ApiQuery({ - name: 'cursorId', - required: false, - type: 'number', - }), - ApiResponse({ - status: 200, - description: '전공 강의 조회 성공 시', + description: '강의 검색 성공 시', type: PaginatedCoursesDto, }), ApiKukeyExceptionResponse(['MAJOR_REQUIRED']), - ], - getAcademicFoundationCourses: [ - ApiBearerAuth('accessToken'), - ApiOperation({ - summary: '학문의 기초 강의 조회', - description: '해당 단과대의 모든 학문의 기초 강의를 조회합니다.', - }), - ApiQuery({ - name: 'college', - required: true, - type: 'string', - }), - ApiQuery({ - name: 'cursorId', - required: false, - type: 'number', - }), - ApiResponse({ - status: 200, - description: '학문의 기초 강의 조회 성공 시', - type: PaginatedCoursesDto, - }), ApiKukeyExceptionResponse(['COLLEGE_REQUIRED']), ], - getCourse: [ - ApiOperation({ - summary: '특정 강의 조회', - description: '특정 강의를 조회합니다.', - }), - ApiParam({ - name: 'courseId', - description: '특정 강의 ID', - }), - ApiResponse({ - status: 200, - description: '특정 강의 조회 성공 시', - type: CommonCourseResponseDto, - }), - ApiKukeyExceptionResponse(['COURSE_NOT_FOUND']), - ], }; export function CourseDocs(target: typeof CourseController) { From 6ed09fa9664b9b82a1b1f83671c32eef9bf0214a Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:46:45 +0900 Subject: [PATCH 05/13] =?UTF-8?q?refactor::=20=EA=B0=95=EC=9D=98=EA=B2=80?= =?UTF-8?q?=EC=83=89=20=EC=A0=84=EB=9E=B5=20=EC=83=9D=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../academic-foundation-search-strategy.ts | 64 +++++++++++++++++-- .../strategy/all-courses-search-strategy.ts | 54 ++++++++++++++++ src/course/strategy/course-search-strategy.ts | 9 +++ .../strategy/general-search-strategy.ts | 58 +++++++++++++++-- src/course/strategy/major-search-strategy.ts | 64 +++++++++++++++++-- 5 files changed, 234 insertions(+), 15 deletions(-) create mode 100644 src/course/strategy/all-courses-search-strategy.ts create mode 100644 src/course/strategy/course-search-strategy.ts diff --git a/src/course/strategy/academic-foundation-search-strategy.ts b/src/course/strategy/academic-foundation-search-strategy.ts index 89623af..84008d6 100644 --- a/src/course/strategy/academic-foundation-search-strategy.ts +++ b/src/course/strategy/academic-foundation-search-strategy.ts @@ -1,9 +1,63 @@ -import { SearchStrategy } from './search-strategy'; +import { CourseCategory } from 'src/enums/course-category.enum'; +import { CourseSearchStrategy } from './course-search-strategy'; +import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; +import { SearchCourseNewDto } from '../dto/search-course-new.dto'; +import { throwKukeyException } from 'src/utils/exception.util'; +import { CourseService } from '../course.service'; +import { Brackets } from 'typeorm'; +import { Injectable } from '@nestjs/common'; -const ACADEMIC_FOUNDATION = 'Academic Foundations'; +@Injectable() +export class AcademicFoundationSearchStrategy implements CourseSearchStrategy { + constructor(private readonly courseService: CourseService) {} + supports(category: CourseCategory): boolean { + return category === CourseCategory.ACADEMIC_FOUNDATIONS; + } + + async search( + searchCourseNewDto: SearchCourseNewDto, + ): Promise { + if (!searchCourseNewDto.classification) { + throwKukeyException('COLLEGE_REQUIRED'); + } + const courseRepository = await this.courseService.getCourseRepository(); + const { keyword, cursorId, year, semester } = searchCourseNewDto; + + const LIMIT = PaginatedCoursesDto.LIMIT; + + let queryBuilder = courseRepository + .createQueryBuilder('course') + .leftJoinAndSelect('course.courseDetails', 'courseDetails') + .where('course.year = :year', { year }) + .andWhere('course.semester = :semester', { semester }) + .andWhere('course.category = :category', { + category: CourseCategory.ACADEMIC_FOUNDATIONS, + }) + .andWhere('course.college = :college', { + college: searchCourseNewDto.classification, + }); + + queryBuilder = queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) + .orWhere('course.professorName LIKE :keyword', { + keyword: `%${keyword}%`, + }) + .orWhere('course.courseCode LIKE :keyword', { + keyword: `%${keyword}%`, + }); + }), + ); + + if (cursorId) { + queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { + cursorId, + }); + } + + queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); -export class AcademicFoundationSearchStrategy implements SearchStrategy { - supports(category: string): boolean { - return category === ACADEMIC_FOUNDATION; + const courses = await queryBuilder.getMany(); + return await this.courseService.mappingCourseDetailsToCourses(courses); } } diff --git a/src/course/strategy/all-courses-search-strategy.ts b/src/course/strategy/all-courses-search-strategy.ts new file mode 100644 index 0000000..10cc4f5 --- /dev/null +++ b/src/course/strategy/all-courses-search-strategy.ts @@ -0,0 +1,54 @@ +import { CourseCategory } from 'src/enums/course-category.enum'; +import { Injectable } from '@nestjs/common'; +import { CourseSearchStrategy } from './course-search-strategy'; +import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; +import { SearchCourseNewDto } from '../dto/search-course-new.dto'; +import { CourseService } from '../course.service'; +import { Brackets } from 'typeorm'; + +@Injectable() +export class AllCoursesSearchStrategy implements CourseSearchStrategy { + constructor(private readonly courseService: CourseService) {} + + supports(category: CourseCategory): boolean { + return category === CourseCategory.ALL_CLASS; + } + + async search( + searchCourseNewDto: SearchCourseNewDto, + ): Promise { + const courseRepository = await this.courseService.getCourseRepository(); + const { keyword, cursorId, year, semester } = searchCourseNewDto; + + const LIMIT = PaginatedCoursesDto.LIMIT; + + let queryBuilder = courseRepository + .createQueryBuilder('course') + .leftJoinAndSelect('course.courseDetails', 'courseDetails') + .where('course.year = :year', { year }) + .andWhere('course.semester = :semester', { semester }); + + queryBuilder = queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) + .orWhere('course.professorName LIKE :keyword', { + keyword: `%${keyword}%`, + }) + .orWhere('course.courseCode LIKE :keyword', { + keyword: `%${keyword}%`, + }); + }), + ); + + if (cursorId) { + queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { + cursorId, + }); + } + + queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); + + const courses = await queryBuilder.getMany(); + return await this.courseService.mappingCourseDetailsToCourses(courses); + } +} diff --git a/src/course/strategy/course-search-strategy.ts b/src/course/strategy/course-search-strategy.ts new file mode 100644 index 0000000..e7963d7 --- /dev/null +++ b/src/course/strategy/course-search-strategy.ts @@ -0,0 +1,9 @@ +import { CourseCategory } from 'src/enums/course-category.enum'; +import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; +import { SearchCourseNewDto } from '../dto/search-course-new.dto'; + +export interface CourseSearchStrategy { + supports(category: CourseCategory): boolean; + + search(searchCourseNewDto: SearchCourseNewDto): Promise; +} diff --git a/src/course/strategy/general-search-strategy.ts b/src/course/strategy/general-search-strategy.ts index 6073cb1..a7c786f 100644 --- a/src/course/strategy/general-search-strategy.ts +++ b/src/course/strategy/general-search-strategy.ts @@ -1,9 +1,57 @@ -import { SearchStrategy } from './search-strategy'; +import { CourseCategory } from 'src/enums/course-category.enum'; +import { Injectable } from '@nestjs/common'; +import { CourseSearchStrategy } from './course-search-strategy'; +import { SearchCourseNewDto } from '../dto/search-course-new.dto'; +import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; +import { CourseService } from '../course.service'; +import { Brackets } from 'typeorm'; -const GENERAL = 'General Studies'; +@Injectable() +export class GeneralSearchStrategy implements CourseSearchStrategy { + constructor(private readonly courseService: CourseService) {} -export class GeneralSearchStrategy implements SearchStrategy { - supports(category: string): boolean { - return category === GENERAL; + supports(category: CourseCategory): boolean { + return category === CourseCategory.GENERAL_STUDIES; + } + + async search( + searchCourseNewDto: SearchCourseNewDto, + ): Promise { + const courseRepository = await this.courseService.getCourseRepository(); + const { keyword, cursorId, year, semester } = searchCourseNewDto; + + const LIMIT = PaginatedCoursesDto.LIMIT; + + let queryBuilder = courseRepository + .createQueryBuilder('course') + .leftJoinAndSelect('course.courseDetails', 'courseDetails') + .where('course.year = :year', { year }) + .andWhere('course.semester = :semester', { semester }) + .andWhere('course.category = :category', { + category: CourseCategory.GENERAL_STUDIES, + }); + + queryBuilder = queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) + .orWhere('course.professorName LIKE :keyword', { + keyword: `%${keyword}%`, + }) + .orWhere('course.courseCode LIKE :keyword', { + keyword: `%${keyword}%`, + }); + }), + ); + + if (cursorId) { + queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { + cursorId, + }); + } + + queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); + + const courses = await queryBuilder.getMany(); + return await this.courseService.mappingCourseDetailsToCourses(courses); } } diff --git a/src/course/strategy/major-search-strategy.ts b/src/course/strategy/major-search-strategy.ts index e983f57..0b9520c 100644 --- a/src/course/strategy/major-search-strategy.ts +++ b/src/course/strategy/major-search-strategy.ts @@ -1,9 +1,63 @@ -import { SearchStrategy } from './search-strategy'; +import { Injectable } from '@nestjs/common'; +import { CourseCategory } from 'src/enums/course-category.enum'; +import { CourseSearchStrategy } from './course-search-strategy'; +import { SearchCourseNewDto } from '../dto/search-course-new.dto'; +import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; +import { CourseService } from '../course.service'; +import { throwKukeyException } from 'src/utils/exception.util'; +import { Brackets } from 'typeorm'; -const MAJOR = 'Major'; +@Injectable() +export class MajorSearchStrategy implements CourseSearchStrategy { + constructor(private readonly courseService: CourseService) {} + supports(category: CourseCategory): boolean { + return category === CourseCategory.MAJOR; + } + + async search( + searchCourseNewDto: SearchCourseNewDto, + ): Promise { + if (!searchCourseNewDto.classification) { + throwKukeyException('MAJOR_REQUIRED'); + } + const courseRepository = await this.courseService.getCourseRepository(); + const { keyword, cursorId, year, semester } = searchCourseNewDto; + + const LIMIT = PaginatedCoursesDto.LIMIT; + + let queryBuilder = courseRepository + .createQueryBuilder('course') + .leftJoinAndSelect('course.courseDetails', 'courseDetails') + .where('course.year = :year', { year }) + .andWhere('course.semester = :semester', { semester }) + .andWhere('course.category = :category', { + category: CourseCategory.MAJOR, + }) + .andWhere('course.major = :major', { + major: searchCourseNewDto.classification, + }); + + queryBuilder = queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) + .orWhere('course.professorName LIKE :keyword', { + keyword: `%${keyword}%`, + }) + .orWhere('course.courseCode LIKE :keyword', { + keyword: `%${keyword}%`, + }); + }), + ); + + if (cursorId) { + queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { + cursorId, + }); + } + + queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); -export class MajorSearchStrategy implements SearchStrategy { - supports(category: string): boolean { - return category === MAJOR; + const courses = await queryBuilder.getMany(); + return await this.courseService.mappingCourseDetailsToCourses(courses); } } From 47aecd10980db9068567191163c3b284d4e19ab6 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:47:14 +0900 Subject: [PATCH 06/13] =?UTF-8?q?refactor::=20=EA=B0=95=EC=9D=98=EA=B2=80?= =?UTF-8?q?=EC=83=89=20service=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/course.service.ts | 425 ++---------------------- src/course/dto/search-course-new.dto.ts | 2 +- src/course/strategy/search-strategy.ts | 3 - 3 files changed, 25 insertions(+), 405 deletions(-) delete mode 100644 src/course/strategy/search-strategy.ts diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 686fe7a..6705575 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -1,77 +1,26 @@ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { CourseRepository } from './course.repository'; import { CourseEntity } from 'src/entities/course.entity'; import { CourseDetailEntity } from 'src/entities/course-detail.entity'; import { CourseDetailRepository } from './course-detail.repository'; -import { Brackets, EntityManager, Like, MoreThan } from 'typeorm'; +import { EntityManager, Like } from 'typeorm'; import { CommonCourseResponseDto } from './dto/common-course-response.dto'; -import { SearchCourseCodeDto } from './dto/search-course-code.dto'; -import { SearchCourseNameDto } from './dto/search-course-name.dto'; -import { SearchProfessorNameDto } from './dto/search-professor-name.dto'; import { PaginatedCoursesDto } from './dto/paginated-courses.dto'; import { throwKukeyException } from 'src/utils/exception.util'; -import { GetGeneralCourseDto } from './dto/get-general-course.dto'; -import { GetMajorCourseDto } from './dto/get-major-course.dto'; -import { GetAcademicFoundationCourseDto } from './dto/get-academic-foundation-course.dto'; -import { SearchCoursesWithKeywordDto } from './dto/search-courses-with-keyword.dto'; +import { SearchCourseNewDto } from './dto/search-course-new.dto'; +import { CourseSearchStrategy } from './strategy/course-search-strategy'; @Injectable() export class CourseService { constructor( private courseRepository: CourseRepository, private courseDetailRepository: CourseDetailRepository, + @Inject('CourseSearchStrategy') + private readonly strategies: CourseSearchStrategy[], ) {} - async searchAllCourses( - searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - const courses = await this.runSearchCoursesQuery( - searchCoursesWithKeywordDto, - ); - return await this.mappingCourseDetailsToCourses(courses); - } - - async searchMajorCourses( - major: string, - searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - if (!major) throwKukeyException('MAJOR_REQUIRED'); - - const courses = await this.runSearchCoursesQuery( - searchCoursesWithKeywordDto, - { - major, - category: 'Major', - }, - ); - return await this.mappingCourseDetailsToCourses(courses); - } - - async searchGeneralCourses( - searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - const courses = await this.runSearchCoursesQuery( - searchCoursesWithKeywordDto, - { - category: 'General Studies', - }, - ); - return await this.mappingCourseDetailsToCourses(courses); - } - - async searchAcademicFoundationCourses( - college: string, - searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - ): Promise { - if (!college) throwKukeyException('COLLEGE_REQUIRED'); - const courses = await this.runSearchCoursesQuery( - searchCoursesWithKeywordDto, - { - college, - category: 'Academic Foundations', - }, - ); - return await this.mappingCourseDetailsToCourses(courses); + async getCourseRepository(): Promise { + return this.courseRepository; } async getCourse(courseId: number): Promise { @@ -124,298 +73,6 @@ export class CourseService { }); } - // 학수번호 검색 - async searchCourseCode( - searchCourseCodeDto: SearchCourseCodeDto, - ): Promise { - let courses: CourseEntity[] = []; - if (searchCourseCodeDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - courseCode: Like(`${searchCourseCodeDto.courseCode}%`), - id: MoreThan(searchCourseCodeDto.cursorId), - year: searchCourseCodeDto.year, - semester: searchCourseCodeDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - courseCode: Like(`${searchCourseCodeDto.courseCode}%`), - year: searchCourseCodeDto.year, - semester: searchCourseCodeDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - return await this.mappingCourseDetailsToCourses(courses); - } - - // 전공 과목명 검색 (최소 3글자 이상 입력 ) - async searchMajorCourseName( - major: string, - searchCourseNameDto: SearchCourseNameDto, - ): Promise { - if (!major) throwKukeyException('MAJOR_REQUIRED'); - - let courses = []; - - if (searchCourseNameDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - courseName: Like(`%${searchCourseNameDto.courseName}%`), - major: major, - category: 'Major', - id: MoreThan(searchCourseNameDto.cursorId), - year: searchCourseNameDto.year, - semester: searchCourseNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - courseName: Like(`%${searchCourseNameDto.courseName}%`), - major: major, - category: 'Major', - year: searchCourseNameDto.year, - semester: searchCourseNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - - return await this.mappingCourseDetailsToCourses(courses); - } - - // 전공 교수님 성함 검색 - async searchMajorProfessorName( - major: string, - searchProfessorNameDto: SearchProfessorNameDto, - ): Promise { - if (!major) { - throwKukeyException('MAJOR_REQUIRED'); - } - let courses = []; - - if (searchProfessorNameDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - professorName: Like(`%${searchProfessorNameDto.professorName}%`), - major: major, - category: 'Major', - id: MoreThan(searchProfessorNameDto.cursorId), - year: searchProfessorNameDto.year, - semester: searchProfessorNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - professorName: Like(`%${searchProfessorNameDto.professorName}%`), - major: major, - category: 'Major', - year: searchProfessorNameDto.year, - semester: searchProfessorNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - - return await this.mappingCourseDetailsToCourses(courses); - } - - // 교양 과목명 검색 (최소 3글자 이상 입력) - async searchGeneralCourseName( - searchCourseNameDto: SearchCourseNameDto, - ): Promise { - let courses = []; - - if (searchCourseNameDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - courseName: Like(`%${searchCourseNameDto.courseName}%`), - category: 'General Studies', - id: MoreThan(searchCourseNameDto.cursorId), - year: searchCourseNameDto.year, - semester: searchCourseNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - courseName: Like(`%${searchCourseNameDto.courseName}%`), - category: 'General Studies', - year: searchCourseNameDto.year, - semester: searchCourseNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - - return await this.mappingCourseDetailsToCourses(courses); - } - - // 교양 교수님 성함 검색 - async searchGeneralProfessorName( - searchProfessorNameDto: SearchProfessorNameDto, - ): Promise { - let courses = []; - - if (searchProfessorNameDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - professorName: Like(`%${searchProfessorNameDto.professorName}%`), - category: 'General Studies', - id: MoreThan(searchProfessorNameDto.cursorId), - year: searchProfessorNameDto.year, - semester: searchProfessorNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - professorName: Like(`%${searchProfessorNameDto.professorName}%`), - category: 'General Studies', - year: searchProfessorNameDto.year, - semester: searchProfessorNameDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - - return await this.mappingCourseDetailsToCourses(courses); - } - - // 교양 리스트 반환 - async getGeneralCourses( - getGeneralCourseDto: GetGeneralCourseDto, - ): Promise { - let courses = []; - if (getGeneralCourseDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - category: 'General Studies', - id: MoreThan(getGeneralCourseDto.cursorId), - year: getGeneralCourseDto.year, - semester: getGeneralCourseDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - category: 'General Studies', - year: getGeneralCourseDto.year, - semester: getGeneralCourseDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - - return await this.mappingCourseDetailsToCourses(courses); - } - - // 전공 리스트 반환 - async getMajorCourses( - getMajorCourseDto: GetMajorCourseDto, - ): Promise { - if (!getMajorCourseDto.major) throwKukeyException('MAJOR_REQUIRED'); - let courses = []; - if (getMajorCourseDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - category: 'Major', - major: getMajorCourseDto.major, - id: MoreThan(getMajorCourseDto.cursorId), - year: getMajorCourseDto.year, - semester: getMajorCourseDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - category: 'Major', - major: getMajorCourseDto.major, - year: getMajorCourseDto.year, - semester: getMajorCourseDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - - return await this.mappingCourseDetailsToCourses(courses); - } - - // 학문의 기초 리스트 반환 - async getAcademicFoundationCourses( - getAcademicFoundationCourseDto: GetAcademicFoundationCourseDto, - ): Promise { - if (!getAcademicFoundationCourseDto.college) - throwKukeyException('COLLEGE_REQUIRED'); - let courses = []; - if (getAcademicFoundationCourseDto.cursorId) { - courses = await this.courseRepository.find({ - where: { - category: 'Academic Foundations', - college: getAcademicFoundationCourseDto.college, - id: MoreThan(getAcademicFoundationCourseDto.cursorId), - year: getAcademicFoundationCourseDto.year, - semester: getAcademicFoundationCourseDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } else { - courses = await this.courseRepository.find({ - where: { - category: 'Academic Foundations', - college: getAcademicFoundationCourseDto.college, - year: getAcademicFoundationCourseDto.year, - semester: getAcademicFoundationCourseDto.semester, - }, - order: { id: 'ASC' }, - take: 21, - relations: ['courseDetails'], - }); - } - return await this.mappingCourseDetailsToCourses(courses); - } - async updateCourseTotalRate( courseIds: number[], totalRate: number, @@ -437,60 +94,26 @@ export class CourseService { return new PaginatedCoursesDto(courseInformations); } - private async runSearchCoursesQuery( - searchCoursesWithKeywordDto: SearchCoursesWithKeywordDto, - options?: { major?: string; college?: string; category?: string }, - ): Promise { - const { keyword, cursorId, year, semester } = searchCoursesWithKeywordDto; - - const LIMIT = PaginatedCoursesDto.LIMIT; - - let queryBuilder = this.courseRepository - .createQueryBuilder('course') - .leftJoinAndSelect('course.courseDetails', 'courseDetails') - .where('course.year = :year', { year }) - .andWhere('course.semester = :semester', { semester }); - - // Optional: 추가 조건 적용 - if (options?.major) { - queryBuilder = queryBuilder.andWhere('course.major = :major', { - major: options.major, - }); - } - - if (options?.college) { - queryBuilder = queryBuilder.andWhere('course.college = :college', { - college: options.college, - }); - } - - if (options?.category) { - queryBuilder = queryBuilder.andWhere('course.category = :category', { - category: options.category, - }); - } + async searchCourses( + searchCourseNewDto: SearchCourseNewDto, + ): Promise { + // 해당하는 검색 전략 찾아오기 + const searchStrategy = await this.findSearchStrategy(searchCourseNewDto); + return await searchStrategy.search(searchCourseNewDto); + } - // 검색 조건(LIKE) - queryBuilder = queryBuilder.andWhere( - new Brackets((qb) => { - qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) - .orWhere('course.professorName LIKE :keyword', { - keyword: `%${keyword}%`, - }) - .orWhere('course.courseCode LIKE :keyword', { - keyword: `%${keyword}%`, - }); - }), + private async findSearchStrategy( + searchCourseNewDto: SearchCourseNewDto, + ): Promise { + const { category } = searchCourseNewDto; + const searchStrategy = this.strategies.find((strategy) => + strategy.supports(category), ); - if (cursorId) { - queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { - cursorId, - }); + if (!searchStrategy) { + throwKukeyException('COURSE_SEARCH_STRATEGY_NOT_FOUND'); } - queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); - - return await queryBuilder.getMany(); + return searchStrategy; } } diff --git a/src/course/dto/search-course-new.dto.ts b/src/course/dto/search-course-new.dto.ts index 120a93f..cbcd812 100644 --- a/src/course/dto/search-course-new.dto.ts +++ b/src/course/dto/search-course-new.dto.ts @@ -36,7 +36,7 @@ export class SearchCourseNewDto { @ApiPropertyOptional({ description: - 'Major일때 major를, Academic Foundation일 때 college를 넣어주세요.', + 'cateogry가 Major일때 특정 과를, category가 Academic Foundation일 때 특정 단과대를 넣어주세요.', }) @IsString() @IsOptional() diff --git a/src/course/strategy/search-strategy.ts b/src/course/strategy/search-strategy.ts deleted file mode 100644 index d254910..0000000 --- a/src/course/strategy/search-strategy.ts +++ /dev/null @@ -1,3 +0,0 @@ -export interface SearchStrategy { - supports(category: string): boolean; -} From 1dbcec1817194c9407e12bd13263847910347c0f Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Mon, 27 Jan 2025 21:47:27 +0900 Subject: [PATCH 07/13] =?UTF-8?q?refactor::=20=EB=AA=A8=EB=93=88=20?= =?UTF-8?q?=EB=93=B1=EB=A1=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/course.module.ts | 34 +++++++++++++++++++++++++++++++++- 1 file changed, 33 insertions(+), 1 deletion(-) diff --git a/src/course/course.module.ts b/src/course/course.module.ts index 6b21e6e..082e538 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -6,11 +6,43 @@ import { CourseDetailRepository } from './course-detail.repository'; import { TypeOrmModule } from '@nestjs/typeorm'; import { CourseEntity } from 'src/entities/course.entity'; import { CourseDetailEntity } from 'src/entities/course-detail.entity'; +import { AcademicFoundationSearchStrategy } from './strategy/academic-foundation-search-strategy'; +import { AllCoursesSearchStrategy } from './strategy/all-courses-search-strategy'; +import { GeneralSearchStrategy } from './strategy/general-search-strategy'; +import { MajorSearchStrategy } from './strategy/major-search-strategy'; @Module({ imports: [TypeOrmModule.forFeature([CourseEntity, CourseDetailEntity])], controllers: [CourseController], - providers: [CourseService, CourseRepository, CourseDetailRepository], + providers: [ + CourseService, + CourseRepository, + CourseDetailRepository, + AcademicFoundationSearchStrategy, + AllCoursesSearchStrategy, + GeneralSearchStrategy, + MajorSearchStrategy, + { + provide: 'CourseSearchStrategy', + useFactory: ( + academicFoundationSearchStrategy: AcademicFoundationSearchStrategy, + allCoursesSearchStrategy: AllCoursesSearchStrategy, + generalSearchStrategy: GeneralSearchStrategy, + majorSearchStrategy: MajorSearchStrategy, + ) => [ + academicFoundationSearchStrategy, + allCoursesSearchStrategy, + generalSearchStrategy, + majorSearchStrategy, + ], + inject: [ + AcademicFoundationSearchStrategy, + AllCoursesSearchStrategy, + GeneralSearchStrategy, + MajorSearchStrategy, + ], + }, + ], exports: [CourseService], }) export class CourseModule {} From 8f85ce52cfe54c4c109d515ab326f60f61933863 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Tue, 28 Jan 2025 02:03:41 +0900 Subject: [PATCH 08/13] =?UTF-8?q?refactor::=20=ED=95=84=EC=9A=94=EC=97=86?= =?UTF-8?q?=EB=8A=94=20CourseCategory=20enum=EA=B0=92=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/enums/course-category.enum.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/enums/course-category.enum.ts b/src/enums/course-category.enum.ts index 4199f8e..6449734 100644 --- a/src/enums/course-category.enum.ts +++ b/src/enums/course-category.enum.ts @@ -1,5 +1,4 @@ export enum CourseCategory { - ALL_CLASS = 'All Class', MAJOR = 'Major', GENERAL_STUDIES = 'General Studies', ACADEMIC_FOUNDATIONS = 'Academic Foundations', From 4679553aecccf77a5a3d125e710d61565d8d03c0 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Tue, 28 Jan 2025 02:04:48 +0900 Subject: [PATCH 09/13] =?UTF-8?q?refactor::=20enum=20=EC=88=98=EC=A0=95?= =?UTF-8?q?=EC=97=90=20=EB=94=B0=EB=A5=B8=20dto=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/dto/search-course-new.dto.ts | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/course/dto/search-course-new.dto.ts b/src/course/dto/search-course-new.dto.ts index cbcd812..a6ec3b7 100644 --- a/src/course/dto/search-course-new.dto.ts +++ b/src/course/dto/search-course-new.dto.ts @@ -20,12 +20,15 @@ export class SearchCourseNewDto { @Length(1) semester: string; - @ApiProperty({ - description: '강의 카테고리 (모든 강의, 전공, 교양, 학문의 기초)', + @ApiPropertyOptional({ + description: + '강의 카테고리 (모든 강의, 전공, 교양, 학문의 기초), 모든 강의는 값을 넘겨주지 않음', enum: CourseCategory, + nullable: true, }) + @IsOptional() @IsEnum(CourseCategory) - category: CourseCategory; + category?: CourseCategory; @ApiPropertyOptional({ description: '검색 키워드 (강의명, 교수명, 학수번호)', From 420362900a2b70d5ba578234e027ff83ced3cae9 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Tue, 28 Jan 2025 02:06:32 +0900 Subject: [PATCH 10/13] =?UTF-8?q?refactor::=20=EC=88=9C=ED=99=98=20?= =?UTF-8?q?=EC=9D=98=EC=A1=B4=EC=84=B1=20=ED=95=B4=EA=B2=B0=20=EB=B0=8F=20?= =?UTF-8?q?=EA=B2=80=EC=83=89=20=EB=A1=9C=EC=A7=81=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/course.module.ts | 10 ++-- src/course/course.service.ts | 45 +++++++++++++++- .../academic-foundation-search-strategy.ts | 48 +++-------------- .../strategy/all-courses-search-strategy.ts | 50 +++-------------- src/course/strategy/course-search-strategy.ts | 8 ++- .../strategy/general-search-strategy.ts | 53 +++---------------- src/course/strategy/major-search-strategy.ts | 48 +++-------------- 7 files changed, 86 insertions(+), 176 deletions(-) diff --git a/src/course/course.module.ts b/src/course/course.module.ts index 082e538..eecf640 100644 --- a/src/course/course.module.ts +++ b/src/course/course.module.ts @@ -7,9 +7,9 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CourseEntity } from 'src/entities/course.entity'; import { CourseDetailEntity } from 'src/entities/course-detail.entity'; import { AcademicFoundationSearchStrategy } from './strategy/academic-foundation-search-strategy'; -import { AllCoursesSearchStrategy } from './strategy/all-courses-search-strategy'; import { GeneralSearchStrategy } from './strategy/general-search-strategy'; import { MajorSearchStrategy } from './strategy/major-search-strategy'; +import { AllCoursesSearchStrategy } from './strategy/all-courses-search-strategy'; @Module({ imports: [TypeOrmModule.forFeature([CourseEntity, CourseDetailEntity])], @@ -19,27 +19,27 @@ import { MajorSearchStrategy } from './strategy/major-search-strategy'; CourseRepository, CourseDetailRepository, AcademicFoundationSearchStrategy, - AllCoursesSearchStrategy, GeneralSearchStrategy, MajorSearchStrategy, + AllCoursesSearchStrategy, { provide: 'CourseSearchStrategy', useFactory: ( academicFoundationSearchStrategy: AcademicFoundationSearchStrategy, - allCoursesSearchStrategy: AllCoursesSearchStrategy, generalSearchStrategy: GeneralSearchStrategy, majorSearchStrategy: MajorSearchStrategy, + allCoursesSearchStrategy: AllCoursesSearchStrategy, ) => [ academicFoundationSearchStrategy, - allCoursesSearchStrategy, generalSearchStrategy, majorSearchStrategy, + allCoursesSearchStrategy, ], inject: [ AcademicFoundationSearchStrategy, - AllCoursesSearchStrategy, GeneralSearchStrategy, MajorSearchStrategy, + AllCoursesSearchStrategy, ], }, ], diff --git a/src/course/course.service.ts b/src/course/course.service.ts index 6705575..c27268d 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -3,7 +3,7 @@ import { CourseRepository } from './course.repository'; import { CourseEntity } from 'src/entities/course.entity'; import { CourseDetailEntity } from 'src/entities/course-detail.entity'; import { CourseDetailRepository } from './course-detail.repository'; -import { EntityManager, Like } from 'typeorm'; +import { Brackets, EntityManager, Like } from 'typeorm'; import { CommonCourseResponseDto } from './dto/common-course-response.dto'; import { PaginatedCoursesDto } from './dto/paginated-courses.dto'; import { throwKukeyException } from 'src/utils/exception.util'; @@ -97,9 +97,50 @@ export class CourseService { async searchCourses( searchCourseNewDto: SearchCourseNewDto, ): Promise { + const { keyword, cursorId } = searchCourseNewDto; + const LIMIT = PaginatedCoursesDto.LIMIT; // 해당하는 검색 전략 찾아오기 const searchStrategy = await this.findSearchStrategy(searchCourseNewDto); - return await searchStrategy.search(searchCourseNewDto); + + let queryBuilder = this.courseRepository + .createQueryBuilder('course') + .leftJoinAndSelect('course.courseDetails', 'courseDetails') + .where('course.year = :year', { year: searchCourseNewDto.year }) + .andWhere('course.semester = :semester', { + semester: searchCourseNewDto.semester, + }); + + queryBuilder = await searchStrategy.buildQuery( + queryBuilder, + searchCourseNewDto, + ); + + if (keyword) { + queryBuilder = queryBuilder.andWhere( + new Brackets((qb) => { + qb.where('course.courseName LIKE :keyword', { + keyword: `%${keyword}%`, + }) + .orWhere('course.professorName LIKE :keyword', { + keyword: `%${keyword}%`, + }) + .orWhere('course.courseCode LIKE :keyword', { + keyword: `%${keyword}%`, + }); + }), + ); + } + + if (cursorId) { + queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { + cursorId, + }); + } + + queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); + + const courses = await queryBuilder.getMany(); + return await this.mappingCourseDetailsToCourses(courses); } private async findSearchStrategy( diff --git a/src/course/strategy/academic-foundation-search-strategy.ts b/src/course/strategy/academic-foundation-search-strategy.ts index 84008d6..3910d8e 100644 --- a/src/course/strategy/academic-foundation-search-strategy.ts +++ b/src/course/strategy/academic-foundation-search-strategy.ts @@ -1,63 +1,31 @@ import { CourseCategory } from 'src/enums/course-category.enum'; import { CourseSearchStrategy } from './course-search-strategy'; -import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; import { SearchCourseNewDto } from '../dto/search-course-new.dto'; import { throwKukeyException } from 'src/utils/exception.util'; -import { CourseService } from '../course.service'; -import { Brackets } from 'typeorm'; +import { SelectQueryBuilder } from 'typeorm'; import { Injectable } from '@nestjs/common'; +import { CourseEntity } from 'src/entities/course.entity'; @Injectable() export class AcademicFoundationSearchStrategy implements CourseSearchStrategy { - constructor(private readonly courseService: CourseService) {} supports(category: CourseCategory): boolean { return category === CourseCategory.ACADEMIC_FOUNDATIONS; } - async search( + async buildQuery( + queryBuilder: SelectQueryBuilder, searchCourseNewDto: SearchCourseNewDto, - ): Promise { + ): Promise> { if (!searchCourseNewDto.classification) { throwKukeyException('COLLEGE_REQUIRED'); } - const courseRepository = await this.courseService.getCourseRepository(); - const { keyword, cursorId, year, semester } = searchCourseNewDto; - const LIMIT = PaginatedCoursesDto.LIMIT; + const { classification } = searchCourseNewDto; - let queryBuilder = courseRepository - .createQueryBuilder('course') - .leftJoinAndSelect('course.courseDetails', 'courseDetails') - .where('course.year = :year', { year }) - .andWhere('course.semester = :semester', { semester }) + return queryBuilder .andWhere('course.category = :category', { category: CourseCategory.ACADEMIC_FOUNDATIONS, }) - .andWhere('course.college = :college', { - college: searchCourseNewDto.classification, - }); - - queryBuilder = queryBuilder.andWhere( - new Brackets((qb) => { - qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) - .orWhere('course.professorName LIKE :keyword', { - keyword: `%${keyword}%`, - }) - .orWhere('course.courseCode LIKE :keyword', { - keyword: `%${keyword}%`, - }); - }), - ); - - if (cursorId) { - queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { - cursorId, - }); - } - - queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); - - const courses = await queryBuilder.getMany(); - return await this.courseService.mappingCourseDetailsToCourses(courses); + .andWhere('course.college = :college', { college: classification }); } } diff --git a/src/course/strategy/all-courses-search-strategy.ts b/src/course/strategy/all-courses-search-strategy.ts index 10cc4f5..eda531a 100644 --- a/src/course/strategy/all-courses-search-strategy.ts +++ b/src/course/strategy/all-courses-search-strategy.ts @@ -1,54 +1,20 @@ import { CourseCategory } from 'src/enums/course-category.enum'; import { Injectable } from '@nestjs/common'; import { CourseSearchStrategy } from './course-search-strategy'; -import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; +import { CourseEntity } from 'src/entities/course.entity'; +import { SelectQueryBuilder } from 'typeorm'; import { SearchCourseNewDto } from '../dto/search-course-new.dto'; -import { CourseService } from '../course.service'; -import { Brackets } from 'typeorm'; @Injectable() export class AllCoursesSearchStrategy implements CourseSearchStrategy { - constructor(private readonly courseService: CourseService) {} - supports(category: CourseCategory): boolean { - return category === CourseCategory.ALL_CLASS; + return !category; } - async search( - searchCourseNewDto: SearchCourseNewDto, - ): Promise { - const courseRepository = await this.courseService.getCourseRepository(); - const { keyword, cursorId, year, semester } = searchCourseNewDto; - - const LIMIT = PaginatedCoursesDto.LIMIT; - - let queryBuilder = courseRepository - .createQueryBuilder('course') - .leftJoinAndSelect('course.courseDetails', 'courseDetails') - .where('course.year = :year', { year }) - .andWhere('course.semester = :semester', { semester }); - - queryBuilder = queryBuilder.andWhere( - new Brackets((qb) => { - qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) - .orWhere('course.professorName LIKE :keyword', { - keyword: `%${keyword}%`, - }) - .orWhere('course.courseCode LIKE :keyword', { - keyword: `%${keyword}%`, - }); - }), - ); - - if (cursorId) { - queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { - cursorId, - }); - } - - queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); - - const courses = await queryBuilder.getMany(); - return await this.courseService.mappingCourseDetailsToCourses(courses); + async buildQuery( + queryBuilder: SelectQueryBuilder, + searchCourseNewDto?: SearchCourseNewDto, + ): Promise> { + return queryBuilder; } } diff --git a/src/course/strategy/course-search-strategy.ts b/src/course/strategy/course-search-strategy.ts index e7963d7..0cab2a0 100644 --- a/src/course/strategy/course-search-strategy.ts +++ b/src/course/strategy/course-search-strategy.ts @@ -1,9 +1,13 @@ import { CourseCategory } from 'src/enums/course-category.enum'; -import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; import { SearchCourseNewDto } from '../dto/search-course-new.dto'; +import { SelectQueryBuilder } from 'typeorm'; +import { CourseEntity } from 'src/entities/course.entity'; export interface CourseSearchStrategy { supports(category: CourseCategory): boolean; - search(searchCourseNewDto: SearchCourseNewDto): Promise; + buildQuery( + queryBuilder: SelectQueryBuilder, + searchCourseNewDto?: SearchCourseNewDto, + ): Promise>; } diff --git a/src/course/strategy/general-search-strategy.ts b/src/course/strategy/general-search-strategy.ts index a7c786f..026e6b4 100644 --- a/src/course/strategy/general-search-strategy.ts +++ b/src/course/strategy/general-search-strategy.ts @@ -1,57 +1,20 @@ import { CourseCategory } from 'src/enums/course-category.enum'; import { Injectable } from '@nestjs/common'; import { CourseSearchStrategy } from './course-search-strategy'; -import { SearchCourseNewDto } from '../dto/search-course-new.dto'; -import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; -import { CourseService } from '../course.service'; -import { Brackets } from 'typeorm'; +import { SelectQueryBuilder } from 'typeorm'; +import { CourseEntity } from 'src/entities/course.entity'; @Injectable() export class GeneralSearchStrategy implements CourseSearchStrategy { - constructor(private readonly courseService: CourseService) {} - supports(category: CourseCategory): boolean { return category === CourseCategory.GENERAL_STUDIES; } - async search( - searchCourseNewDto: SearchCourseNewDto, - ): Promise { - const courseRepository = await this.courseService.getCourseRepository(); - const { keyword, cursorId, year, semester } = searchCourseNewDto; - - const LIMIT = PaginatedCoursesDto.LIMIT; - - let queryBuilder = courseRepository - .createQueryBuilder('course') - .leftJoinAndSelect('course.courseDetails', 'courseDetails') - .where('course.year = :year', { year }) - .andWhere('course.semester = :semester', { semester }) - .andWhere('course.category = :category', { - category: CourseCategory.GENERAL_STUDIES, - }); - - queryBuilder = queryBuilder.andWhere( - new Brackets((qb) => { - qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) - .orWhere('course.professorName LIKE :keyword', { - keyword: `%${keyword}%`, - }) - .orWhere('course.courseCode LIKE :keyword', { - keyword: `%${keyword}%`, - }); - }), - ); - - if (cursorId) { - queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { - cursorId, - }); - } - - queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); - - const courses = await queryBuilder.getMany(); - return await this.courseService.mappingCourseDetailsToCourses(courses); + async buildQuery( + queryBuilder: SelectQueryBuilder, + ): Promise> { + return queryBuilder.andWhere('course.category = :category', { + category: CourseCategory.GENERAL_STUDIES, + }); } } diff --git a/src/course/strategy/major-search-strategy.ts b/src/course/strategy/major-search-strategy.ts index 0b9520c..9d4643b 100644 --- a/src/course/strategy/major-search-strategy.ts +++ b/src/course/strategy/major-search-strategy.ts @@ -2,62 +2,30 @@ import { Injectable } from '@nestjs/common'; import { CourseCategory } from 'src/enums/course-category.enum'; import { CourseSearchStrategy } from './course-search-strategy'; import { SearchCourseNewDto } from '../dto/search-course-new.dto'; -import { PaginatedCoursesDto } from '../dto/paginated-courses.dto'; -import { CourseService } from '../course.service'; import { throwKukeyException } from 'src/utils/exception.util'; -import { Brackets } from 'typeorm'; +import { SelectQueryBuilder } from 'typeorm'; +import { CourseEntity } from 'src/entities/course.entity'; @Injectable() export class MajorSearchStrategy implements CourseSearchStrategy { - constructor(private readonly courseService: CourseService) {} supports(category: CourseCategory): boolean { return category === CourseCategory.MAJOR; } - async search( + async buildQuery( + queryBuilder: SelectQueryBuilder, searchCourseNewDto: SearchCourseNewDto, - ): Promise { + ): Promise> { if (!searchCourseNewDto.classification) { throwKukeyException('MAJOR_REQUIRED'); } - const courseRepository = await this.courseService.getCourseRepository(); - const { keyword, cursorId, year, semester } = searchCourseNewDto; - const LIMIT = PaginatedCoursesDto.LIMIT; + const { classification } = searchCourseNewDto; - let queryBuilder = courseRepository - .createQueryBuilder('course') - .leftJoinAndSelect('course.courseDetails', 'courseDetails') - .where('course.year = :year', { year }) - .andWhere('course.semester = :semester', { semester }) + return queryBuilder .andWhere('course.category = :category', { category: CourseCategory.MAJOR, }) - .andWhere('course.major = :major', { - major: searchCourseNewDto.classification, - }); - - queryBuilder = queryBuilder.andWhere( - new Brackets((qb) => { - qb.where('course.courseName LIKE :keyword', { keyword: `%${keyword}%` }) - .orWhere('course.professorName LIKE :keyword', { - keyword: `%${keyword}%`, - }) - .orWhere('course.courseCode LIKE :keyword', { - keyword: `%${keyword}%`, - }); - }), - ); - - if (cursorId) { - queryBuilder = queryBuilder.andWhere('course.id > :cursorId', { - cursorId, - }); - } - - queryBuilder = queryBuilder.orderBy('course.id', 'ASC').take(LIMIT); - - const courses = await queryBuilder.getMany(); - return await this.courseService.mappingCourseDetailsToCourses(courses); + .andWhere('course.major = :major', { major: classification }); } } From 423fad8d7459ae28f2fcbeee9cd1765f2c4295d6 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:48:25 +0900 Subject: [PATCH 11/13] =?UTF-8?q?refactor::=20=EC=84=B1=ED=98=84's=20revie?= =?UTF-8?q?w=20=EB=B0=98=EC=98=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/course/course.service.ts | 5 ----- src/course/dto/search-course-new.dto.ts | 4 ++-- src/decorators/docs/course.decorator.ts | 2 ++ 3 files changed, 4 insertions(+), 7 deletions(-) diff --git a/src/course/course.service.ts b/src/course/course.service.ts index c27268d..f7517a4 100644 --- a/src/course/course.service.ts +++ b/src/course/course.service.ts @@ -18,11 +18,6 @@ export class CourseService { @Inject('CourseSearchStrategy') private readonly strategies: CourseSearchStrategy[], ) {} - - async getCourseRepository(): Promise { - return this.courseRepository; - } - async getCourse(courseId: number): Promise { const course = await this.courseRepository.findOne({ where: { id: courseId }, diff --git a/src/course/dto/search-course-new.dto.ts b/src/course/dto/search-course-new.dto.ts index a6ec3b7..f67effb 100644 --- a/src/course/dto/search-course-new.dto.ts +++ b/src/course/dto/search-course-new.dto.ts @@ -12,12 +12,12 @@ export class SearchCourseNewDto { @ApiProperty({ description: '연도' }) @IsString() - @Length(4) + @Length(4, 4) year: string; @ApiProperty({ description: '학기' }) @IsString() - @Length(1) + @Length(1, 1) semester: string; @ApiPropertyOptional({ diff --git a/src/decorators/docs/course.decorator.ts b/src/decorators/docs/course.decorator.ts index 49c335e..5ec3d7c 100644 --- a/src/decorators/docs/course.decorator.ts +++ b/src/decorators/docs/course.decorator.ts @@ -8,6 +8,7 @@ import { MethodNames } from 'src/common/types/method'; import { CourseController } from 'src/course/course.controller'; import { PaginatedCoursesDto } from 'src/course/dto/paginated-courses.dto'; import { ApiKukeyExceptionResponse } from '../api-kukey-exception-response'; +import { CourseCategory } from 'src/enums/course-category.enum'; type CourseEndPoints = MethodNames; @@ -37,6 +38,7 @@ const CourseDocsMap: Record = { name: 'category', required: true, type: 'enum', + enum: CourseCategory, }), ApiQuery({ name: 'keyword', From 7d0f2ca74100cc5fbcdf35b1f79068bdabcc6395 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Sun, 2 Feb 2025 15:54:32 +0900 Subject: [PATCH 12/13] =?UTF-8?q?fix::=20swagger=20=EB=AA=85=EC=84=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 - category는 안 넘겨줄때도 있어서 false로 수정 --- src/decorators/docs/course.decorator.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/decorators/docs/course.decorator.ts b/src/decorators/docs/course.decorator.ts index 5ec3d7c..4cf1349 100644 --- a/src/decorators/docs/course.decorator.ts +++ b/src/decorators/docs/course.decorator.ts @@ -36,7 +36,7 @@ const CourseDocsMap: Record = { }), ApiQuery({ name: 'category', - required: true, + required: false, type: 'enum', enum: CourseCategory, }), From 605b6db6a1010964c06d1d2d9e48c886357b8f84 Mon Sep 17 00:00:00 2001 From: Devheun <86945971+Devheun@users.noreply.github.com> Date: Tue, 4 Feb 2025 23:53:41 +0900 Subject: [PATCH 13/13] =?UTF-8?q?fix:=20=EC=98=A4=ED=83=80=20=EC=88=98?= =?UTF-8?q?=EC=A0=95=20=EB=B0=8F=20swagger=20=EB=AA=85=EC=84=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 --- src/course/dto/search-course-new.dto.ts | 2 +- src/decorators/docs/course.decorator.ts | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/src/course/dto/search-course-new.dto.ts b/src/course/dto/search-course-new.dto.ts index f67effb..f4fb7ba 100644 --- a/src/course/dto/search-course-new.dto.ts +++ b/src/course/dto/search-course-new.dto.ts @@ -39,7 +39,7 @@ export class SearchCourseNewDto { @ApiPropertyOptional({ description: - 'cateogry가 Major일때 특정 과를, category가 Academic Foundation일 때 특정 단과대를 넣어주세요.', + 'category가 Major일때 특정 과를, category가 Academic Foundation일 때 특정 단과대를 넣어주세요.', }) @IsString() @IsOptional() diff --git a/src/decorators/docs/course.decorator.ts b/src/decorators/docs/course.decorator.ts index 4cf1349..16f74d3 100644 --- a/src/decorators/docs/course.decorator.ts +++ b/src/decorators/docs/course.decorator.ts @@ -55,8 +55,7 @@ const CourseDocsMap: Record = { description: '강의 검색 성공 시', type: PaginatedCoursesDto, }), - ApiKukeyExceptionResponse(['MAJOR_REQUIRED']), - ApiKukeyExceptionResponse(['COLLEGE_REQUIRED']), + ApiKukeyExceptionResponse(['MAJOR_REQUIRED', 'COLLEGE_REQUIRED']), ], };