diff --git a/src/common/file.service.ts b/src/common/file.service.ts index 531cb5af..93774f21 100644 --- a/src/common/file.service.ts +++ b/src/common/file.service.ts @@ -1,5 +1,6 @@ import { DeleteObjectCommand, + ListObjectsV2Command, PutObjectCommand, S3Client, } from '@aws-sdk/client-s3'; @@ -64,6 +65,35 @@ export class FileService { } } + // S3 내 특정 경로의 파일 URL들을 가져오는 함수 + async getFileUrls(prefix: string): Promise { + const command = new ListObjectsV2Command({ + Bucket: this.bucketName, + Prefix: prefix, + }); + + const response = await this.s3.send(command); + + if (response.$metadata.httpStatusCode !== 200) { + throw new BadRequestException('Failed get file metadata'); + } + + if (!response.Contents) { + return []; + } + + return response.Contents.filter((object) => this.isFile(object.Key)).map( + (object) => { + return this.makeUrlByFileDir(object.Key); + }, + ); + } + + // 파일 확장자가 있는 경우에만 true를 반환 + isFile(key: string): boolean { + return !!key.split('/').pop().includes('.'); + } + imagefilter(file: Express.Multer.File): boolean { const filetype = file.mimetype.split('/'); return filetype[0] === 'image'; @@ -75,4 +105,9 @@ export class FileService { fileDir ); } + + getFileDirFromUrl(url: string): string { + const baseUrl = `https://${this.configService.get('AWS_BUCKET_NAME')}.s3.${this.configService.get('AWS_BUCKET_REGION')}.amazonaws.com/`; + return url.slice(baseUrl.length); + } } diff --git a/src/community/comment/comment.repository.ts b/src/community/comment/comment.repository.ts index e0862255..53fc02f3 100644 --- a/src/community/comment/comment.repository.ts +++ b/src/community/comment/comment.repository.ts @@ -11,7 +11,7 @@ export class CommentRepository extends Repository { async getCommentByCommentId(commentId: number) { const comment = await this.findOne({ where: { id: commentId }, - relations: ['parentComment', 'user.character', 'commentLikes'], + relations: ['parentComment', 'user.character', 'commentLikes', 'post'], }); return comment; diff --git a/src/community/comment/comment.service.ts b/src/community/comment/comment.service.ts index 0e525af0..d9ffb9bd 100644 --- a/src/community/comment/comment.service.ts +++ b/src/community/comment/comment.service.ts @@ -113,7 +113,7 @@ export class CommentService { if (myAnonymousNumber) { anonymousNumber = myAnonymousNumber.anonymousNumber; } else { - if (post.userId === user.id) { + if (post.isAnonymous && post.userId === user.id) { anonymousNumber = 0; } else { const recentAnonymousNumber = await transactionManager.findOne( @@ -142,7 +142,7 @@ export class CommentService { const createdComment = await transactionManager.findOne(CommentEntity, { where: { id: newCommentId }, - relations: ['parentComment', 'user.character', 'commentLikes'], + relations: ['parentComment', 'user.character', 'commentLikes', 'post'], }); if (post.userId !== user.id) { await this.noticeService.emitNotice( diff --git a/src/community/comment/dto/get-comment.dto.ts b/src/community/comment/dto/get-comment.dto.ts index 639928fa..4d370334 100644 --- a/src/community/comment/dto/get-comment.dto.ts +++ b/src/community/comment/dto/get-comment.dto.ts @@ -6,7 +6,7 @@ export class GetCommentResponseDto { constructor( commentEntity: CommentEntity, userId: number, - anonymousNumber: number, + anonymousNumber?: number, ) { this.id = commentEntity.id; this.isDeleted = commentEntity.deletedAt ? true : false; @@ -14,6 +14,7 @@ export class GetCommentResponseDto { this.updatedAt = commentEntity.updatedAt; if (this.isDeleted) { this.isMyComment = false; + this.isAuthor = false; this.content = null; this.user = { username: null, @@ -25,6 +26,9 @@ export class GetCommentResponseDto { this.myLike = false; } else { this.isMyComment = commentEntity.userId === userId; + this.isAuthor = + commentEntity.post.isAnonymous === commentEntity.isAnonymous && + commentEntity.post.userId === commentEntity.userId; this.content = commentEntity.content; this.user = new CommunityUser( commentEntity.user, @@ -52,6 +56,9 @@ export class GetCommentResponseDto { @ApiProperty({ description: '본인이 작성한 댓글인지 여부' }) isMyComment?: boolean; + @ApiProperty({ description: '게시글 작성자의 댓글인지 여부' }) + isAuthor?: boolean; + @ApiProperty({ description: '댓글 내용' }) content?: string; diff --git a/src/community/post/dto/get-post.dto.ts b/src/community/post/dto/get-post.dto.ts index cbcced9a..24d391ea 100644 --- a/src/community/post/dto/get-post.dto.ts +++ b/src/community/post/dto/get-post.dto.ts @@ -10,7 +10,7 @@ class Comment extends GetCommentResponseDto { constructor( commentEntity: CommentEntity, userId: number, - anonymousNumber: number, + anonymousNumber?: number, ) { super(commentEntity, userId, anonymousNumber); if (!commentEntity.parentCommentId) { @@ -85,7 +85,8 @@ export class GetPostResponseDto { const anonymousNumber = postEntity.commentAnonymousNumbers.filter( (commentAnonymousNumber) => commentAnonymousNumber.userId === comment.userId, - )[0].anonymousNumber; + )[0]?.anonymousNumber; + comment.post = postEntity; if (!comment.parentCommentId) { this.comments.push(new Comment(comment, userId, anonymousNumber)); } else { diff --git a/src/home/calendar/calendar.controller.ts b/src/home/calendar/calendar.controller.ts index f982f24d..f7c31e1d 100644 --- a/src/home/calendar/calendar.controller.ts +++ b/src/home/calendar/calendar.controller.ts @@ -20,14 +20,8 @@ import { ApiQuery, ApiTags, } from '@nestjs/swagger'; -import { - GetDailyCalendarDataResponseDto, - GetMonthlyCalendarDataResponseDto, -} from './dto/get-calendar-data-response-dto'; -import { - GetMonthlyCalendarDataRequestDto, - GetYearlyCalendarDataRequestDto, -} from './dto/get-calendar-data-request-dto'; +import { GetDailyCalendarDataResponseDto } from './dto/get-calendar-data-response-dto'; +import { GetMonthlyCalendarDataRequestDto } from './dto/get-calendar-data-request-dto'; import { CreateCalendarDataRequestDto } from './dto/create-calendar-data-request.dto'; import { CreateCalendarDataResponseDto } from './dto/create-calendar-data-response.dto'; import { UpdateCalendarDataRequestDto } from './dto/update-calendar-data-request.dto'; @@ -39,6 +33,7 @@ import { Roles } from 'src/decorators/roles.decorator'; import { Role } from 'src/enums/role.enum'; import { GetAcademicScheduleDataRequestDto } from './dto/get-academic-schedule-request.dto'; import { GetAcademicScheduleDataResponseDto } from './dto/get-academic-schedule-response.dto'; +import { GetBannerImageUrlResponseDto } from './dto/get-banner-images-response.dto'; @Controller('calendar') @ApiTags('calendar') @@ -89,27 +84,6 @@ export class CalendarController { ); } - @UseGuards(JwtAuthGuard, RolesGuard) - @Roles(Role.admin) - @Get('yearly') - @ApiBearerAuth('accessToken') - @ApiOperation({ - summary: '연도별 행사/일정 전체 조회', - description: - '연도별 행사/일정 전체를 조회합니다. 행사/일정이 존재하는 날짜의 경우에만 가져옵니다.', - }) - @ApiQuery({ name: 'year', required: true, description: '연도' }) - @ApiOkResponse({ - description: '특정 연도별 행사/일정 데이터 반환', - isArray: true, - type: GetMonthlyCalendarDataResponseDto, - }) - async getYearlyCalendarData( - @Query() queryDto: GetYearlyCalendarDataRequestDto, - ): Promise { - return await this.calendarService.getYearlyCalendarData(queryDto.year); - } - @UseGuards(JwtAuthGuard, RolesGuard) @Roles(Role.admin) @Post() @@ -170,4 +144,17 @@ export class CalendarController { ): Promise { return await this.calendarService.deleteCalendarData(calendarId); } + + @Get('banner-image-urls') + @ApiOperation({ + summary: '메인 홈 배너 이미지 URL 목록 조회', + description: 'S3에 저장된 메인 홈 배너 이미지 URL 목록을 조회합니다.', + }) + @ApiOkResponse({ + description: 'URL 목록 반환', + type: [GetBannerImageUrlResponseDto], + }) + async getBannerImageUrls(): Promise { + return await this.calendarService.getBannerImageUrls(); + } } diff --git a/src/home/calendar/calendar.module.ts b/src/home/calendar/calendar.module.ts index b533ffea..d2d8ca0a 100644 --- a/src/home/calendar/calendar.module.ts +++ b/src/home/calendar/calendar.module.ts @@ -5,9 +5,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { CalendarRepository } from './calendar.repository'; import { CalendarEntity } from 'src/entities/calendar.entity'; import { UserRepository } from 'src/user/user.repository'; +import { CommonModule } from 'src/common/common.module'; @Module({ - imports: [TypeOrmModule.forFeature([CalendarEntity])], + imports: [TypeOrmModule.forFeature([CalendarEntity]), CommonModule], providers: [CalendarService, CalendarRepository, UserRepository], controllers: [CalendarController], }) diff --git a/src/home/calendar/calendar.service.ts b/src/home/calendar/calendar.service.ts index 7c277063..e4b5a318 100644 --- a/src/home/calendar/calendar.service.ts +++ b/src/home/calendar/calendar.service.ts @@ -5,22 +5,22 @@ import { } from '@nestjs/common'; import { InjectRepository } from '@nestjs/typeorm'; import { CalendarRepository } from './calendar.repository'; -import { - GetDailyCalendarDataResponseDto, - GetMonthlyCalendarDataResponseDto, -} from './dto/get-calendar-data-response-dto'; +import { GetDailyCalendarDataResponseDto } from './dto/get-calendar-data-response-dto'; import { CreateCalendarDataRequestDto } from './dto/create-calendar-data-request.dto'; import { CreateCalendarDataResponseDto } from './dto/create-calendar-data-response.dto'; import { UpdateCalendarDataRequestDto } from './dto/update-calendar-data-request.dto'; import { UpdateCalendarDataResponseDto } from './dto/update-calendar-data-response.dto'; import { DeleteCalendarDataResponseDto } from './dto/delete-calendar-data-response-dto'; import { GetAcademicScheduleDataResponseDto } from './dto/get-academic-schedule-response.dto'; +import { GetBannerImageUrlResponseDto } from './dto/get-banner-images-response.dto'; +import { FileService } from 'src/common/file.service'; @Injectable() export class CalendarService { constructor( @InjectRepository(CalendarRepository) private readonly calendarRepository: CalendarRepository, + private readonly fileService: FileService, ) {} async getMonthlyCalendarData( @@ -57,22 +57,6 @@ export class CalendarService { return monthCalendarData; } - async getYearlyCalendarData(year: number) { - const allCalendarData: GetMonthlyCalendarDataResponseDto[] = []; - for (let month = 1; month <= 12; month++) { - const monthCalendarData = await this.getMonthlyCalendarData(year, month); - const filteredData = monthCalendarData.filter( - (dayCalendarData) => - dayCalendarData.date.getMonth() === month - 1 && - dayCalendarData.eventCount !== 0, - ); - allCalendarData.push( - new GetMonthlyCalendarDataResponseDto(month, filteredData), - ); - } - return allCalendarData; - } - async getAcademicScheduleData( year: number, semester: number, @@ -163,6 +147,15 @@ export class CalendarService { return new DeleteCalendarDataResponseDto(true); } + async getBannerImageUrls(): Promise { + const prefix = 'fe/home/homeBanners/'; + const imageUrls = await this.fileService.getFileUrls(prefix); + + return imageUrls.map((imageUrl) => { + return new GetBannerImageUrlResponseDto(imageUrl); + }); + } + // 연도, 월 정보를 받아 캘린더의 시작 - 끝 날짜를 반환하는 함수 private getStartAndEndDate( year: number, diff --git a/src/home/calendar/dto/get-academic-schedule-response.dto.ts b/src/home/calendar/dto/get-academic-schedule-response.dto.ts index 3d34d779..58c18522 100644 --- a/src/home/calendar/dto/get-academic-schedule-response.dto.ts +++ b/src/home/calendar/dto/get-academic-schedule-response.dto.ts @@ -12,6 +12,9 @@ enum DayOfWeek { } class AcademicSchedule { + @ApiProperty({ description: '행사/일정 id' }) + id: number; + @ApiProperty({ description: '행사/일정 제목' }) title: string; @@ -31,6 +34,7 @@ class AcademicSchedule { endDay: string; constructor(calendar: CalendarEntity) { + this.id = calendar.id; this.title = calendar.title; this.description = calendar.description; this.startDate = calendar.startDate; diff --git a/src/home/calendar/dto/get-banner-images-response.dto.ts b/src/home/calendar/dto/get-banner-images-response.dto.ts new file mode 100644 index 00000000..ca98ffec --- /dev/null +++ b/src/home/calendar/dto/get-banner-images-response.dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class GetBannerImageUrlResponseDto { + @ApiProperty({ description: 'S3에 저장된 배너 이미지 url' }) + imageUrl: string; + + constructor(imageUrl: string) { + this.imageUrl = imageUrl; + } +} diff --git a/src/home/calendar/dto/get-calendar-data-request-dto.ts b/src/home/calendar/dto/get-calendar-data-request-dto.ts index 00a202af..0f34989d 100644 --- a/src/home/calendar/dto/get-calendar-data-request-dto.ts +++ b/src/home/calendar/dto/get-calendar-data-request-dto.ts @@ -16,12 +16,3 @@ export class GetMonthlyCalendarDataRequestDto { @ApiProperty({ description: '조회할 월' }) month: number; } - -export class GetYearlyCalendarDataRequestDto { - @IsInt() - @IsNotEmpty() - @Max(2030) - @Min(2024) - @ApiProperty({ description: '조회할 연도' }) - year: number; -} diff --git a/src/home/calendar/dto/get-calendar-data-response-dto.ts b/src/home/calendar/dto/get-calendar-data-response-dto.ts index 62720c10..fd4b3c02 100644 --- a/src/home/calendar/dto/get-calendar-data-response-dto.ts +++ b/src/home/calendar/dto/get-calendar-data-response-dto.ts @@ -36,19 +36,3 @@ export class GetDailyCalendarDataResponseDto { this.eventCount = this.event.length; } } - -export class GetMonthlyCalendarDataResponseDto { - @ApiProperty({ description: '월' }) - month: number; - - @ApiProperty({ - description: '월별 행사/일정', - type: [GetDailyCalendarDataResponseDto], - }) - monthEvents: GetDailyCalendarDataResponseDto[]; - - constructor(month: number, monthEvents: GetDailyCalendarDataResponseDto[]) { - this.month = month; - this.monthEvents = monthEvents; - } -} diff --git a/src/home/club/club.controller.ts b/src/home/club/club.controller.ts index 7b780b36..c0a4f173 100644 --- a/src/home/club/club.controller.ts +++ b/src/home/club/club.controller.ts @@ -1,15 +1,21 @@ import { + Body, Controller, + Delete, Get, Param, + Patch, Post, Query, + UploadedFile, UseGuards, UseInterceptors, } from '@nestjs/common'; import { ClubService } from './club.service'; import { ApiBearerAuth, + ApiBody, + ApiConsumes, ApiCreatedResponse, ApiOkResponse, ApiOperation, @@ -29,6 +35,15 @@ import { GetRecommendClubRequestDto } from './dto/get-recommend-club-request.dto import { TransactionInterceptor } from 'src/common/interceptors/transaction.interceptor'; import { TransactionManager } from 'src/decorators/manager.decorator'; import { EntityManager } from 'typeorm'; +import { RolesGuard } from 'src/auth/guards/role.guard'; +import { Role } from 'src/enums/role.enum'; +import { Roles } from 'src/decorators/roles.decorator'; +import { CreateClubRequestDto } from './dto/create-club-request-dto'; +import { FileInterceptor } from '@nestjs/platform-express'; +import { CreateClubResponseDto } from './dto/create-club-response-dto'; +import { UpdateClubRequestDto } from './dto/update-club-request-dto'; +import { UpdateClubResponseDto } from './dto/update-club-response-dto'; +import { DeleteClubResponseDto } from './dto/delete-club-response-dto'; @Controller('club') @ApiTags('club') @@ -151,4 +166,66 @@ export class ClubController { getRecommendClubRequestDto, ); } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(Role.admin) + @UseInterceptors(FileInterceptor('clubImage')) + @Post() + @ApiConsumes('multipart/form-data') + @ApiOperation({ + summary: '동아리 생성', + description: 'Admin page에서 새로운 동아리를 생성합니다.', + }) + @ApiBody({ type: CreateClubRequestDto }) + @ApiCreatedResponse({ + description: '동아리 생성 성공', + type: CreateClubResponseDto, + }) + async createClub( + @UploadedFile() clubImage: Express.Multer.File, + @Body() body: CreateClubRequestDto, + ): Promise { + return await this.clubService.createClub(clubImage, body); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(Role.admin) + @UseInterceptors(FileInterceptor('clubImage')) + @Patch('/:clubId') + @ApiConsumes('multipart/form-data') + @ApiOperation({ + summary: '동아리 정보 수정', + description: '동아리 id를 받아 admin page에서 동아리 정보를 수정합니다.', + }) + @ApiParam({ name: 'clubId', description: '동아리 id' }) + @ApiBody({ type: UpdateClubRequestDto }) + @ApiOkResponse({ + description: '동아리 정보 수정 성공', + type: UpdateClubResponseDto, + }) + async updateClub( + @Param('clubId') clubId: number, + @UploadedFile() clubImage: Express.Multer.File, + @Body() body: UpdateClubRequestDto, + ) { + return await this.clubService.updateClub(clubId, clubImage, body); + } + + @UseGuards(JwtAuthGuard, RolesGuard) + @Roles(Role.admin) + @Delete('/:clubId') + @ApiOperation({ + summary: '동아리 정보 삭제', + description: '동아리 id를 받아 admin page에서 동아리 정보를 삭제합니다.', + }) + @ApiParam({ name: 'clubId', description: '동아리 id' }) + @ApiOkResponse({ + description: '동아리 정보 삭제 성공', + type: DeleteClubResponseDto, + }) + async deleteClub( + @Param('clubId') clubId: number, + ): Promise { + return await this.clubService.deleteClub(clubId); + } } diff --git a/src/home/club/club.module.ts b/src/home/club/club.module.ts index 376d6106..2115489d 100644 --- a/src/home/club/club.module.ts +++ b/src/home/club/club.module.ts @@ -1,3 +1,4 @@ +import { UserModule } from 'src/user/user.module'; import { Module } from '@nestjs/common'; import { ClubService } from './club.service'; import { ClubController } from './club.controller'; @@ -5,9 +6,10 @@ import { TypeOrmModule } from '@nestjs/typeorm'; import { ClubEntity } from 'src/entities/club.entity'; import { ClubRepository } from './club.repository'; import { ClubLikeRepository } from './club-like.repository'; +import { CommonModule } from 'src/common/common.module'; @Module({ - imports: [TypeOrmModule.forFeature([ClubEntity])], + imports: [TypeOrmModule.forFeature([ClubEntity]), UserModule, CommonModule], providers: [ClubService, ClubRepository, ClubLikeRepository], controllers: [ClubController], }) diff --git a/src/home/club/club.repository.ts b/src/home/club/club.repository.ts index 09b14a96..682470ad 100644 --- a/src/home/club/club.repository.ts +++ b/src/home/club/club.repository.ts @@ -1,7 +1,7 @@ import { Injectable } from '@nestjs/common'; import { ClubCategory } from 'src/common/types/club-category-type'; import { ClubEntity } from 'src/entities/club.entity'; -import { DataSource, Repository } from 'typeorm'; +import { Brackets, DataSource, Repository } from 'typeorm'; @Injectable() export class ClubRepository extends Repository { @@ -22,11 +22,15 @@ export class ClubRepository extends Repository { } if (keyword) { - queryBuilder - .andWhere('club.name LIKE :keyword', { - keyword: `${keyword}%`, - }) - .orWhere('club.summary LIKE :keyword', { keyword: `%${keyword}%` }); + queryBuilder.andWhere( + new Brackets((qb) => + qb + .where('club.name LIKE :keyword', { + keyword: `${keyword}%`, + }) + .orWhere('club.summary LIKE :keyword', { keyword: `%${keyword}%` }), + ), + ); } if (sortBy === 'like') { diff --git a/src/home/club/club.service.ts b/src/home/club/club.service.ts index 269f6569..9de23a46 100644 --- a/src/home/club/club.service.ts +++ b/src/home/club/club.service.ts @@ -1,6 +1,8 @@ import { ClubLikeRepository } from './club-like.repository'; import { + BadRequestException, Injectable, + InternalServerErrorException, NotFoundException, UnauthorizedException, } from '@nestjs/common'; @@ -14,12 +16,19 @@ import { GetRecommendClubResponseDto } from './dto/get-recommend-club-response.d import { AuthorizedUserDto } from 'src/auth/dto/authorized-user-dto'; import { GetClubRequestDto } from './dto/get-club-request'; import { GetRecommendClubRequestDto } from './dto/get-recommend-club-request.dto'; +import { CreateClubRequestDto } from './dto/create-club-request-dto'; +import { CreateClubResponseDto } from './dto/create-club-response-dto'; +import { FileService } from 'src/common/file.service'; +import { UpdateClubRequestDto } from './dto/update-club-request-dto'; +import { UpdateClubResponseDto } from './dto/update-club-response-dto'; +import { DeleteClubResponseDto } from './dto/delete-club-response-dto'; @Injectable() export class ClubService { constructor( private readonly clubRepository: ClubRepository, private readonly clubLikeRepository: ClubLikeRepository, + private readonly fileService: FileService, ) {} async getClubList( @@ -199,6 +208,111 @@ export class ClubService { return this.shuffleArray(recommendClubList.slice(0, 4)); } + async createClub( + clubImage: Express.Multer.File, + requestDto: CreateClubRequestDto, + ): Promise { + if (!this.fileService.imagefilter(clubImage)) { + throw new BadRequestException('Only image file can be uploaded!'); + } + + const { + name, + category, + summary, + regularMeeting, + recruitmentPeriod, + description, + instagramLink, + youtubeLink, + } = requestDto; + + const filename = await this.fileService.uploadFile( + clubImage, + 'club', + 'image', + ); + + const imageUrl = this.fileService.makeUrlByFileDir(filename); + + const club = this.clubRepository.create({ + name, + category, + summary, + regularMeeting, + recruitmentPeriod, + description, + instagramLink, + youtubeLink, + imageUrl, + }); + + await this.clubRepository.save(club); + + return new CreateClubResponseDto(club); + } + + async updateClub( + clubId: number, + clubImage: Express.Multer.File, + requestDto: UpdateClubRequestDto, + ): Promise { + const club = await this.clubRepository.findOne({ + where: { id: clubId }, + }); + if (!club) { + throw new NotFoundException('동아리 정보를 찾을 수 없습니다.'); + } + + const updateData: any = { ...requestDto }; + delete updateData.clubImage; + let newFilename: string | null = null; + + if (clubImage) { + if (!this.fileService.imagefilter(clubImage)) { + throw new BadRequestException('Only image file can be uploaded!'); + } + const filename = this.fileService.getFileDirFromUrl(club.imageUrl); + await this.fileService.deleteFile(filename); + newFilename = await this.fileService.uploadFile( + clubImage, + 'club', + 'image', + ); + updateData.imageUrl = this.fileService.makeUrlByFileDir(newFilename); + } + + const updated = await this.clubRepository.update( + { id: clubId }, + updateData, + ); + if (updated.affected === 0) { + throw new InternalServerErrorException('업데이트에 실패했습니다.'); + } + + return new UpdateClubResponseDto(true); + } + + async deleteClub(clubId: number): Promise { + const club = await this.clubRepository.findOne({ + where: { id: clubId }, + }); + if (!club) { + throw new NotFoundException('동아리 정보를 찾을 수 없습니다.'); + } + const filename = this.fileService.getFileDirFromUrl(club.imageUrl); + await this.fileService.deleteFile(filename); + + const deleted = await this.clubRepository.softDelete({ id: clubId }); + if (deleted.affected === 0) { + throw new InternalServerErrorException( + '동아리 정보를 삭제하는데 실패했습니다.', + ); + } + + return new DeleteClubResponseDto(true); + } + // 리스트를 랜덤하게 섞어서 반환하는 함수 private shuffleArray(array: any[]): any[] { for (let i = array.length - 1; i > 0; i--) { diff --git a/src/home/club/dto/create-club-request-dto.ts b/src/home/club/dto/create-club-request-dto.ts new file mode 100644 index 00000000..161c5283 --- /dev/null +++ b/src/home/club/dto/create-club-request-dto.ts @@ -0,0 +1,52 @@ +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { IsNotEmpty, IsOptional, IsString } from 'class-validator'; +import { ClubCategory } from 'src/common/types/club-category-type'; + +export class CreateClubRequestDto { + @IsNotEmpty() + @IsString() + @ApiProperty({ description: '동아리명' }) + name: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ description: '카테고리' }) + category: ClubCategory; + + @IsNotEmpty() + @IsString() + @ApiProperty({ description: '동아리 요약' }) + summary: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ description: '정기 모임' }) + regularMeeting: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ description: '모집 기간' }) + recruitmentPeriod: string; + + @IsNotEmpty() + @IsString() + @ApiProperty({ description: '동아리 설명' }) + description: string; + + @ApiProperty({ + type: 'string', + format: 'binary', + description: '동아리 이미지 파일', + }) + clubImage: any; + + @IsOptional() + @IsString() + @ApiPropertyOptional({ description: '인스타그램 링크' }) + instagramLink: string; + + @IsOptional() + @IsString() + @ApiPropertyOptional({ description: '유튜브 링크' }) + youtubeLink: string; +} diff --git a/src/home/club/dto/create-club-response-dto.ts b/src/home/club/dto/create-club-response-dto.ts new file mode 100644 index 00000000..c563eb50 --- /dev/null +++ b/src/home/club/dto/create-club-response-dto.ts @@ -0,0 +1,28 @@ +import { PickType } from '@nestjs/swagger'; +import { GetClubResponseDto } from './get-club-response.dto'; +import { ClubEntity } from 'src/entities/club.entity'; + +export class CreateClubResponseDto extends PickType(GetClubResponseDto, [ + 'clubId', + 'name', + 'summary', + 'regularMeeting', + 'recruitmentPeriod', + 'description', + 'imageUrl', + 'instagramLink', + 'youtubeLink', +]) { + constructor(club: ClubEntity) { + super(); + this.clubId = club.id; + this.name = club.name; + this.summary = club.summary; + this.regularMeeting = club.regularMeeting; + this.recruitmentPeriod = club.recruitmentPeriod; + this.description = club.description; + this.imageUrl = club.imageUrl; + this.instagramLink = club.instagramLink; + this.youtubeLink = club.youtubeLink; + } +} diff --git a/src/home/club/dto/delete-club-response-dto.ts b/src/home/club/dto/delete-club-response-dto.ts new file mode 100644 index 00000000..17a7b118 --- /dev/null +++ b/src/home/club/dto/delete-club-response-dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class DeleteClubResponseDto { + @ApiProperty({ description: '삭제 여부' }) + deleted: boolean; + + constructor(deleted: boolean) { + this.deleted = deleted; + } +} diff --git a/src/home/club/dto/get-club-response.dto.ts b/src/home/club/dto/get-club-response.dto.ts index a62fec7c..a804f1dc 100644 --- a/src/home/club/dto/get-club-response.dto.ts +++ b/src/home/club/dto/get-club-response.dto.ts @@ -1,4 +1,5 @@ import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { ClubCategory } from 'src/common/types/club-category-type'; import { ClubEntity } from 'src/entities/club.entity'; export class GetClubResponseDto { @@ -8,6 +9,9 @@ export class GetClubResponseDto { @ApiProperty({ description: '동아리명' }) name: string; + @ApiProperty({ description: '동아리 카테고리' }) + category: ClubCategory; + @ApiProperty({ description: '동아리 요약' }) summary: string; @@ -38,6 +42,7 @@ export class GetClubResponseDto { constructor(club: ClubEntity, isLiked: boolean) { this.clubId = club.id; this.name = club.name; + this.category = club.category; this.summary = club.summary; this.regularMeeting = club.regularMeeting; this.recruitmentPeriod = club.recruitmentPeriod; diff --git a/src/home/club/dto/update-club-request-dto.ts b/src/home/club/dto/update-club-request-dto.ts new file mode 100644 index 00000000..41cae153 --- /dev/null +++ b/src/home/club/dto/update-club-request-dto.ts @@ -0,0 +1,4 @@ +import { PartialType } from '@nestjs/swagger'; +import { CreateClubRequestDto } from './create-club-request-dto'; + +export class UpdateClubRequestDto extends PartialType(CreateClubRequestDto) {} diff --git a/src/home/club/dto/update-club-response-dto.ts b/src/home/club/dto/update-club-response-dto.ts new file mode 100644 index 00000000..b67a102d --- /dev/null +++ b/src/home/club/dto/update-club-response-dto.ts @@ -0,0 +1,10 @@ +import { ApiProperty } from '@nestjs/swagger'; + +export class UpdateClubResponseDto { + @ApiProperty({ description: '업데이트 여부' }) + updated: boolean; + + constructor(updated: boolean) { + this.updated = updated; + } +}