diff --git a/migrations/1700634679995-notice-board.ts b/migrations/1700634679995-notice-board.ts index a22cac6c..4b4e1879 100644 --- a/migrations/1700634679995-notice-board.ts +++ b/migrations/1700634679995-notice-board.ts @@ -80,7 +80,7 @@ export class NoticeBoard1700634679995 implements MigrationInterface { comment: '조회수', }, { - name: 'allow_comment', + name: 'is_allow_comment', type: 'tinyint', length: '1', unsigned: true, @@ -190,7 +190,7 @@ export class NoticeBoard1700634679995 implements MigrationInterface { comment: '댓글 본문', }, { - name: 'isAnonymous', + name: 'is_anonymous', type: 'tinyint', length: '1', default: 0, @@ -316,7 +316,7 @@ export class NoticeBoard1700634679995 implements MigrationInterface { comment: '대댓글 본문', }, { - name: 'isAnonymous', + name: 'is_anonymous', type: 'tinyint', length: '1', default: 0, diff --git a/migrations/1701328900610-notice-board-history.ts b/migrations/1701328900610-notice-board-history.ts new file mode 100644 index 00000000..8aedd993 --- /dev/null +++ b/migrations/1701328900610-notice-board-history.ts @@ -0,0 +1,241 @@ +import { + MigrationInterface, + QueryRunner, + Table, + TableColumnOptions, +} from 'typeorm'; + +const generatePrimaryColumn = ( + comment: string = '고유 ID', +): TableColumnOptions => { + return { + name: 'id', + type: 'int', + unsigned: true, + isPrimary: true, + isNullable: false, + isGenerated: true, + generationStrategy: 'increment', + comment, + }; +}; + +const generateCreatedAtColumn = ( + comment: string = '생성 일자', +): TableColumnOptions => { + return { + name: 'created_at', + type: 'timestamp', + isNullable: false, + default: 'CURRENT_TIMESTAMP', + comment, + }; +}; + +export class NoticeBoardHistory1701328900610 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + // 공지게시글 히스토리 + await queryRunner.createTable( + new Table({ + name: 'notice_board_history', + columns: [ + generatePrimaryColumn('공지 게시글 히스토리 고유 ID'), + { + name: 'notice_board_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '공지 게시글 고유 ID', + }, + { + name: 'user_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '게시글 작성 유저 고유 ID', + }, + { + name: 'title', + type: 'varchar', + length: '255', + isNullable: false, + comment: '공지게시글 제목', + }, + { + name: 'description', + type: 'text', + isNullable: false, + comment: '공지게시글 내용', + }, + { + name: 'is_allow_comment', + type: 'boolean', + default: 1, + isNullable: false, + comment: '댓글 허용 여부 (0: 비활성화, 1: 허용)', + }, + generateCreatedAtColumn(), + ], + foreignKeys: [ + { + referencedTableName: 'notice_board', + referencedColumnNames: ['id'], + columnNames: ['notice_board_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + { + referencedTableName: 'user', + referencedColumnNames: ['id'], + columnNames: ['user_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + ], + }), + ); + await queryRunner.query( + 'ALTER TABLE notice_board_history COMMENT = "공지 게시판 수정이력"', + ); + + // 공지게시글 댓글 수정이력 + await queryRunner.createTable( + new Table({ + name: 'notice_board_comment_history', + columns: [ + generatePrimaryColumn('공지게시글 댓글 수정이력 고유 ID'), + { + name: 'user_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '댓글 작성 유저 고유 ID', + }, + { + name: 'notice_board_history_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '게시글 고유 ID', + }, + { + name: 'description', + type: 'varchar', + length: '255', + isNullable: false, + comment: '댓글 본문', + }, + { + name: 'is_anonymous', + type: 'boolean', + default: 0, + isNullable: false, + comment: '작성자 익명 여부 (0: 실명, 1: 익명)', + }, + generateCreatedAtColumn(), + ], + foreignKeys: [ + { + referencedTableName: 'user', + referencedColumnNames: ['id'], + columnNames: ['user_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + { + referencedTableName: 'notice_board_history', + referencedColumnNames: ['id'], + columnNames: ['notice_board_history_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + ], + }), + ); + await queryRunner.query( + 'ALTER TABLE notice_board_comment_history COMMENT = "공지 게시글 댓글 수정이력"', + ); + + // 공지 게시글 대댓글 수정이력 + await queryRunner.createTable( + new Table({ + name: 'notice_board_reply_comment_history', + columns: [ + generatePrimaryColumn('공지 게시글 대댓글 수정이력 고유 ID'), + { + name: 'notice_board_history_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '게시글 고유 ID', + }, + { + name: 'notice_board_comment_history_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '공지 게시글 댓글 고유 ID', + }, + { + name: 'user_id', + type: 'int', + unsigned: true, + isNullable: false, + comment: '대댓글 작성 유저 고유 ID', + }, + { + name: 'description', + type: 'varchar', + length: '255', + isNullable: false, + comment: '대댓글 본문', + }, + { + name: 'is_anonymous', + type: 'boolean', + default: 0, + isNullable: false, + comment: '작성자 익명 여부 (0: 실명, 1: 익명)', + }, + generateCreatedAtColumn(), + ], + foreignKeys: [ + { + referencedTableName: 'user', + referencedColumnNames: ['id'], + columnNames: ['user_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + { + referencedTableName: 'notice_board_history', + referencedColumnNames: ['id'], + columnNames: ['notice_board_history_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + { + referencedTableName: 'notice_board_comment_history', + referencedColumnNames: ['id'], + columnNames: ['notice_board_comment_history_id'], + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }, + ], + }), + ); + await queryRunner.query( + 'ALTER TABLE notice_board_reply_comment_history COMMENT = "공지 게시판 대댓글 수정이력"', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropTable( + new Table({ name: 'notice_board_reply_comment_history' }), + ); + await queryRunner.dropTable( + new Table({ name: 'notice_board_comment_history' }), + ); + await queryRunner.dropTable(new Table({ name: 'notice_board_history' })); + } +} diff --git a/migrations/1701696068699-notice-board-change-column-name.ts b/migrations/1701696068699-notice-board-change-column-name.ts new file mode 100644 index 00000000..11b979de --- /dev/null +++ b/migrations/1701696068699-notice-board-change-column-name.ts @@ -0,0 +1,123 @@ +// import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +// export class NoticeBoardChangeColumnName1701696068699 +// implements MigrationInterface +// { +// public async up(queryRunner: QueryRunner): Promise { +// await queryRunner.changeColumn( +// 'notice_board', +// 'allow_comment', +// new TableColumn({ +// name: 'is_allow_comment', +// type: 'boolean', +// default: true, +// isNullable: false, +// comment: '댓글 허용 여부 (0: 비활성화, 1: 허용)', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_comment', +// 'isAnonymous', +// new TableColumn({ +// name: 'is_anonymous', +// type: 'tinyint', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_reply_comment', +// 'isAnonymous', +// new TableColumn({ +// name: 'is_anonymous', +// type: 'tinyint', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_history', +// 'allow_comment', +// new TableColumn({ +// name: 'is_allow_comment', +// type: 'boolean', +// default: true, +// isNullable: false, +// comment: '댓글 허용 여부 (0: 비활성화, 1: 허용)', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_comment_history', +// 'isAnonymous', +// new TableColumn({ +// name: 'is_anonymous', +// type: 'tinyint', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_reply_comment_history', +// 'isAnonymous', +// new TableColumn({ +// name: 'is_anonymous', +// type: 'tinyint', +// }), +// ); +// } + +// public async down(queryRunner: QueryRunner): Promise { +// await queryRunner.changeColumn( +// 'notice_board', +// 'is_allow_comment', +// new TableColumn({ +// name: 'allow_comment', +// type: 'boolean', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_comment', +// 'is_anonymous', +// new TableColumn({ +// name: 'isAnonymous', +// type: 'tinyint', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_reply_comment', +// 'is_anonymous', +// new TableColumn({ +// name: 'isAnonymous', +// type: 'tinyint', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_history', +// 'is_allow_comment', +// new TableColumn({ +// name: 'allow_comment', +// type: 'boolean', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_comment_history', +// 'is_anonymous', +// new TableColumn({ +// name: 'isAnonymous', +// type: 'tinyint', +// }), +// ); + +// await queryRunner.changeColumn( +// 'notice_board_reply_comment_history', +// 'is_anonymous', +// new TableColumn({ +// name: 'isAnonymous', +// type: 'tinyint', +// }), +// ); +// } +// } diff --git a/migrations/1701700089002-notice-board-soft-delete.ts b/migrations/1701700089002-notice-board-soft-delete.ts new file mode 100644 index 00000000..54fcf500 --- /dev/null +++ b/migrations/1701700089002-notice-board-soft-delete.ts @@ -0,0 +1,67 @@ +import { NoticeBoardStatus } from '@src/apis/notice-boards/constants/notice-board.enum'; +import { HistoryAction } from '@src/constants/enum'; +import { MigrationInterface, QueryRunner, TableColumn } from 'typeorm'; + +export class NoticeBoardSoftDelete1701700089002 implements MigrationInterface { + public async up(queryRunner: QueryRunner): Promise { + await queryRunner.addColumns('notice_board', [ + new TableColumn({ + name: 'deleted_at', + type: 'timestamp', + isNullable: true, + comment: '삭제 일자', + }), + new TableColumn({ + name: 'status', + type: 'enum', + enum: [NoticeBoardStatus.Posting, NoticeBoardStatus.Remove], + isNullable: false, + default: `"${NoticeBoardStatus.Posting}"`, + comment: '공지게시글 상태', + }), + ]); + queryRunner.query( + 'ALTER TABLE notice_board ALTER COLUMN status DROP DEFAULT', + ); + + await queryRunner.addColumns('notice_board_history', [ + new TableColumn({ + name: 'action', + type: 'enum', + enum: [ + HistoryAction.Insert, + HistoryAction.Update, + HistoryAction.Delete, + ], + isNullable: false, + default: `"${HistoryAction.Insert}"`, + comment: 'history 를 쌓는 action', + }), + new TableColumn({ + name: 'status', + type: 'enum', + enum: [NoticeBoardStatus.Posting, NoticeBoardStatus.Remove], + default: `"${NoticeBoardStatus.Posting}"`, + isNullable: false, + comment: '공지게시글 상태', + }), + ]); + queryRunner.query( + 'ALTER TABLE notice_board_history ALTER COLUMN action DROP DEFAULT', + ); + queryRunner.query( + 'ALTER TABLE notice_board_history ALTER COLUMN status DROP DEFAULT', + ); + } + + public async down(queryRunner: QueryRunner): Promise { + await queryRunner.dropColumns('notice_board', [ + new TableColumn({ name: 'deleted_at', type: 'timestamp' }), + new TableColumn({ name: 'status', type: 'enum' }), + ]); + await queryRunner.dropColumns('notice_board_history', [ + new TableColumn({ name: 'action', type: 'enum' }), + new TableColumn({ name: 'status', type: 'enum' }), + ]); + } +} diff --git a/src/apis/api.module.ts b/src/apis/api.module.ts index 9ad13d65..6769a03b 100644 --- a/src/apis/api.module.ts +++ b/src/apis/api.module.ts @@ -2,10 +2,11 @@ import { Module } from '@nestjs/common'; import { RootModule } from '@src/apis/root/root.module'; import { AuthModule } from './auth/auth.module'; import { FreeBoardsModule } from './free-boards/free-boards.module'; +import { NoticeBoardsModule } from './notice-boards/notice-boards.module'; import { MajorModule } from './major/major.module'; import { UsersModule } from './users/users.module'; @Module({ - imports: [RootModule, AuthModule, UsersModule, FreeBoardsModule, MajorModule], + imports: [RootModule, AuthModule, UsersModule, FreeBoardsModule, MajorModule, NoticeBoardsModule], }) export class ApiModule {} diff --git a/src/apis/notice-boards/constants/notice-board.constant.ts b/src/apis/notice-boards/constants/notice-board.constant.ts new file mode 100644 index 00000000..81513dc5 --- /dev/null +++ b/src/apis/notice-boards/constants/notice-board.constant.ts @@ -0,0 +1,16 @@ +import { NoticeBoardDto } from '../dto/notice-board.dto'; + +export const NOTICE_BOARD_ORDER_FIELD: readonly (keyof NoticeBoardDto)[] = [ + 'id', + 'userId', + 'title', + 'hit', + 'isAllowComment', + 'createdAt', + 'updatedAt', +] as const; + +export const NOTICE_BOARD_TITLE_LENGTH = { + MIN: 1, + MAX: 255, +} as const; diff --git a/src/apis/notice-boards/constants/notice-board.enum.ts b/src/apis/notice-boards/constants/notice-board.enum.ts new file mode 100644 index 00000000..c2a91b35 --- /dev/null +++ b/src/apis/notice-boards/constants/notice-board.enum.ts @@ -0,0 +1,4 @@ +export enum NoticeBoardStatus { + Posting = 'posting', + Remove = 'remove', +} diff --git a/src/apis/notice-boards/controllers/notice-boards.controller.ts b/src/apis/notice-boards/controllers/notice-boards.controller.ts new file mode 100644 index 00000000..9761811b --- /dev/null +++ b/src/apis/notice-boards/controllers/notice-boards.controller.ts @@ -0,0 +1,63 @@ +import { + Body, + Controller, + Get, + // Delete, + // Get, + // Patch, + Post, + Query, + UseGuards, +} from '@nestjs/common'; +import { CreateNoticeBoardDto } from '../dto/create-notice-board.dto'; +import { NoticeBoardsService } from '../services/notice-boards.service'; +import { SetResponse } from '@src/interceptors/success-interceptor/decorators/success-response.decorator'; +import { ResponseType } from '@src/interceptors/success-interceptor/constants/success-interceptor.enum'; +import { ApiNoticeBoard } from './notice-boards.swagger'; +import { ApiTags } from '@nestjs/swagger'; +import { FindNoticeBoardListQueryDto } from '../dto/find-notice-board-list-query.dto'; +import { NoticeBoardsItemDto } from '../dto/notice-boards-item.dto'; +import { plainToInstance } from 'class-transformer'; +import { JwtAuthGuard } from '@src/apis/auth/jwt/jwt.guard'; +import { UserDto } from '@src/apis/users/dto/user.dto'; +import { User } from '@src/decorators/user.decorator'; + +@ApiTags('notice-boards') +@Controller('notice-boards') +export class NoticeBoardsController { + constructor(private readonly noticeBoardService: NoticeBoardsService) {} + + @ApiNoticeBoard.Create({ summary: '공지 게시글 생성 API' }) + @UseGuards(JwtAuthGuard) + @SetResponse({ type: ResponseType.Detail, key: 'noticeBoard' }) + @Post() + create( + @User() user: UserDto, + @Body() createNoticeBoardDto: CreateNoticeBoardDto, + ) { + return this.noticeBoardService.create(user.id, createNoticeBoardDto); + } + + @ApiNoticeBoard.FindAllAndCount({ + summary: '공지 게시글 전체조회(pagination)', + }) + @SetResponse({ type: ResponseType.Pagination, key: 'noticeBoards' }) + @Get() + async findAllAndCount( + @Query() findNoticeBoardListQueryDto: FindNoticeBoardListQueryDto, + ): Promise<[NoticeBoardsItemDto[], number]> { + const [noticeBoards, count] = await this.noticeBoardService.findAllAndCount( + findNoticeBoardListQueryDto, + ); + + return [plainToInstance(NoticeBoardsItemDto, noticeBoards), count]; + } + // @Get(':id') + // findOne() {} + + // @Patch(':id') + // update() {} + + // @Delete(':id') + // remove() {} +} diff --git a/src/apis/notice-boards/controllers/notice-boards.swagger.ts b/src/apis/notice-boards/controllers/notice-boards.swagger.ts new file mode 100644 index 00000000..b4ab218a --- /dev/null +++ b/src/apis/notice-boards/controllers/notice-boards.swagger.ts @@ -0,0 +1,72 @@ +import { HttpStatus, applyDecorators } from '@nestjs/common'; +import { ApiBearerAuth, ApiOperation } from '@nestjs/swagger'; +import { OperationObject } from '@nestjs/swagger/dist/interfaces/open-api-spec.interface'; +import { COMMON_ERROR_CODE } from '@src/constants/error/common/common-error-code.constant'; +import { HttpException } from '@src/http-exceptions/exceptions/http.exception'; +import { ApiOperator } from '@src/types/type'; +import { ValidationError } from '@src/types/validation-errors.type'; +import { NoticeBoardsController } from './notice-boards.controller'; +import { DetailResponseDto } from '@src/interceptors/success-interceptor/dto/detail-response.dto'; +import { NoticeBoardDto } from '../dto/notice-board.dto'; +import { NoticeBoardsItemDto } from '../dto/notice-boards-item.dto'; +import { PaginationResponseDto } from '@src/interceptors/success-interceptor/dto/pagination-response.dto'; + +export const ApiNoticeBoard: ApiOperator = { + Create: ( + apiOperationOptions: Required, 'summary'>> & + Partial, + ): PropertyDecorator => { + return applyDecorators( + ApiOperation({ + operationId: 'NoticeBoardCreate', + ...apiOperationOptions, + }), + ApiBearerAuth(), + DetailResponseDto.swaggerBuilder( + HttpStatus.CREATED, + 'noticeBoard', + NoticeBoardDto, + ), + HttpException.swaggerBuilder( + HttpStatus.BAD_REQUEST, + [COMMON_ERROR_CODE.INVALID_REQUEST_PARAMETER], + { + description: + '해당 필드는 request parameter 가 잘못된 경우에만 리턴됩니다.', + type: ValidationError, + }, + ), + HttpException.swaggerBuilder(HttpStatus.UNAUTHORIZED, [ + COMMON_ERROR_CODE.INVALID_TOKEN, + ]), + HttpException.swaggerBuilder(HttpStatus.INTERNAL_SERVER_ERROR, [ + COMMON_ERROR_CODE.SERVER_ERROR, + ]), + ); + }, + FindAllAndCount: ( + apiOperationOptions: Required, 'summary'>> & + Partial, + ): PropertyDecorator => { + return applyDecorators( + ApiOperation({ + operationId: 'NoticeBoardFindAllAndCount', + ...apiOperationOptions, + }), + PaginationResponseDto.swaggerBuilder( + HttpStatus.OK, + 'noticeBoards', + NoticeBoardsItemDto, + ), + HttpException.swaggerBuilder( + HttpStatus.BAD_REQUEST, + [COMMON_ERROR_CODE.INVALID_REQUEST_PARAMETER], + { + description: + '해당 필드는 request parameter 가 잘못된 경우에만 리턴됩니다.', + type: ValidationError, + }, + ), + ); + }, +}; diff --git a/src/apis/notice-boards/dto/create-notice-board.dto.ts b/src/apis/notice-boards/dto/create-notice-board.dto.ts new file mode 100644 index 00000000..a2f391a4 --- /dev/null +++ b/src/apis/notice-boards/dto/create-notice-board.dto.ts @@ -0,0 +1,28 @@ +import { IsBoolean, IsNotEmpty, Length } from 'class-validator'; +import { NOTICE_BOARD_TITLE_LENGTH } from '../constants/notice-board.constant'; +import { NoticeBoardDto } from './notice-board.dto'; +import { ApiProperty } from '@nestjs/swagger'; + +export class CreateNoticeBoardDto + implements Pick +{ + @ApiProperty({ + description: '공지 게시글 제목', + minLength: NOTICE_BOARD_TITLE_LENGTH.MIN, + maxLength: NOTICE_BOARD_TITLE_LENGTH.MAX, + }) + @Length(NOTICE_BOARD_TITLE_LENGTH.MIN, NOTICE_BOARD_TITLE_LENGTH.MAX) + title: string; + + @ApiProperty({ + description: '공지 게시글 본문', + }) + @IsNotEmpty() + description: string; + + @ApiProperty({ + description: '댓글 허용 여부 (false: 비활성화, true: 허용)', + }) + @IsBoolean() + isAllowComment: boolean; +} diff --git a/src/apis/notice-boards/dto/find-notice-board-list-query.dto.ts b/src/apis/notice-boards/dto/find-notice-board-list-query.dto.ts new file mode 100644 index 00000000..5b585de1 --- /dev/null +++ b/src/apis/notice-boards/dto/find-notice-board-list-query.dto.ts @@ -0,0 +1,64 @@ +import { PageDto } from '@src/dto/page.dto'; +import { NoticeBoardDto } from './notice-board.dto'; +import { ApiProperty, ApiPropertyOptional } from '@nestjs/swagger'; +import { + IsBooleanString, + IsDefined, + IsOptional, + Length, +} from 'class-validator'; +import { IsPositiveInt } from '@src/dto/validator/is-positive-int.decorator'; +import { + NOTICE_BOARD_ORDER_FIELD, + NOTICE_BOARD_TITLE_LENGTH, +} from '../constants/notice-board.constant'; +import { ApiPropertyOrder } from '@src/dto/swagger/api-property-order.decorator'; +import { CsvToOrder, Order } from '@src/dto/transformer/csv-to-order.decorator'; +import { SortOrder } from '@src/constants/enum'; +import { NoticeBoardStatus } from '../constants/notice-board.enum'; + +export class FindNoticeBoardListQueryDto + extends PageDto + implements Partial +{ + @ApiPropertyOptional({ + description: '공지게시글 고유 ID 필터링', + format: 'integer', + }) + @IsOptional() + @IsPositiveInt() + id?: number; + + @ApiPropertyOptional({ + description: '공지게시글 작성자 고유 ID 필터링', + format: 'integer', + }) + @IsOptional() + @IsPositiveInt() + userId?: number; + + @ApiPropertyOptional({ + description: 'title 필터링', + minLength: NOTICE_BOARD_TITLE_LENGTH.MIN, + maxLength: NOTICE_BOARD_TITLE_LENGTH.MAX, + }) + @Length(NOTICE_BOARD_TITLE_LENGTH.MIN, NOTICE_BOARD_TITLE_LENGTH.MAX) + @IsOptional() + title?: string; + + @ApiPropertyOptional({ + description: '댓글 허용 여부', + enum: ['true', 'false'], + }) + @IsBooleanString() + @IsOptional() + isAllowComment?: boolean; + + @ApiPropertyOrder(NOTICE_BOARD_ORDER_FIELD) + @CsvToOrder([...NOTICE_BOARD_ORDER_FIELD]) + @IsOptional() + order: Order = { id: SortOrder.Desc }; + + @IsDefined() + status: NoticeBoardStatus.Posting = NoticeBoardStatus.Posting; +} diff --git a/src/apis/notice-boards/dto/notice-board.dto.ts b/src/apis/notice-boards/dto/notice-board.dto.ts new file mode 100644 index 00000000..83d2a8aa --- /dev/null +++ b/src/apis/notice-boards/dto/notice-board.dto.ts @@ -0,0 +1,67 @@ +import { ApiProperty } from '@nestjs/swagger'; +import { BaseDto } from '@src/dto/base.dto'; +import { NoticeBoard } from '@src/entities/NoticeBoard'; +import { NOTICE_BOARD_TITLE_LENGTH } from '../constants/notice-board.constant'; +import { Exclude } from 'class-transformer'; +import { NoticeBoardStatus } from '../constants/notice-board.enum'; + +export class NoticeBoardDto + extends BaseDto + implements + Pick< + NoticeBoard, + | 'id' + | 'title' + | 'description' + | 'userId' + | 'hit' + | 'isAllowComment' + | 'status' + | 'createdAt' + | 'updatedAt' + | 'deletedAt' + > +{ + @ApiProperty({ + description: '공지 게시글 제목', + minLength: NOTICE_BOARD_TITLE_LENGTH.MIN, + maxLength: NOTICE_BOARD_TITLE_LENGTH.MAX, + }) + title: string; + + @ApiProperty({ + description: '공지 게시글 본문', + }) + description: string; + + @ApiProperty({ + description: '게시글 작성자 고유 ID', + format: 'integer', + }) + userId: number; + + @ApiProperty({ + description: '공지 게시글 조회수', + default: 0, + format: 'integer', + }) + hit: number; + + @ApiProperty({ + description: '댓글 허용 여부 (false: 비활성화, true: 허용)', + default: true, + }) + isAllowComment: boolean; + + @Exclude() + status: NoticeBoardStatus; + + @Exclude() + deletedAt: Date; + + constructor(noticeBoardDto: Partial = {}) { + super(); + + Object.assign(this, noticeBoardDto); + } +} diff --git a/src/apis/notice-boards/dto/notice-boards-item.dto.ts b/src/apis/notice-boards/dto/notice-boards-item.dto.ts new file mode 100644 index 00000000..118c1c5e --- /dev/null +++ b/src/apis/notice-boards/dto/notice-boards-item.dto.ts @@ -0,0 +1,6 @@ +import { OmitType } from '@nestjs/swagger'; +import { NoticeBoardDto } from './notice-board.dto'; + +export class NoticeBoardsItemDto extends OmitType(NoticeBoardDto, [ + 'description', +] as const) {} diff --git a/src/apis/notice-boards/notice-board-history/dto/create-notice-board-history.dto.ts b/src/apis/notice-boards/notice-board-history/dto/create-notice-board-history.dto.ts new file mode 100644 index 00000000..308637d6 --- /dev/null +++ b/src/apis/notice-boards/notice-board-history/dto/create-notice-board-history.dto.ts @@ -0,0 +1,22 @@ +import { NoticeBoardHistory } from '@src/entities/NoticeBoardHistory'; +import { NoticeBoardStatus } from '../../constants/notice-board.enum'; + +export class CreateNoticeBoardHistoryDto + implements + Pick< + NoticeBoardHistory, + 'title' | 'description' | 'isAllowComment' | 'status' + > +{ + title: string; + description: string; + isAllowComment: boolean; + status: NoticeBoardStatus; + + constructor(createNoticeBoardHistoryDto: CreateNoticeBoardHistoryDto) { + this.title = createNoticeBoardHistoryDto.title; + this.description = createNoticeBoardHistoryDto.description; + this.isAllowComment = createNoticeBoardHistoryDto.isAllowComment; + this.status = createNoticeBoardHistoryDto.status; + } +} diff --git a/src/apis/notice-boards/notice-board-history/notice-board-history.module.ts b/src/apis/notice-boards/notice-board-history/notice-board-history.module.ts new file mode 100644 index 00000000..d37e537a --- /dev/null +++ b/src/apis/notice-boards/notice-board-history/notice-board-history.module.ts @@ -0,0 +1,11 @@ +import { Module } from '@nestjs/common'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { NoticeBoardHistoryService } from './services/notice-board-history.service'; +import { NoticeBoardHistory } from '@src/entities/NoticeBoardHistory'; + +@Module({ + imports: [TypeOrmModule.forFeature([NoticeBoardHistory])], + providers: [NoticeBoardHistoryService], + exports: [NoticeBoardHistoryService], +}) +export class NoticeBoardHistoryModule {} diff --git a/src/apis/notice-boards/notice-board-history/services/notice-board-history.service.ts b/src/apis/notice-boards/notice-board-history/services/notice-board-history.service.ts new file mode 100644 index 00000000..285cd875 --- /dev/null +++ b/src/apis/notice-boards/notice-board-history/services/notice-board-history.service.ts @@ -0,0 +1,30 @@ +import { Injectable } from '@nestjs/common'; +import { InjectRepository } from '@nestjs/typeorm'; +import { NoticeBoardHistory } from '@src/entities/NoticeBoardHistory'; +import { EntityManager, Repository } from 'typeorm'; +import { CreateNoticeBoardHistoryDto } from '../dto/create-notice-board-history.dto'; +import { HistoryAction } from '@src/constants/enum'; + +@Injectable() +export class NoticeBoardHistoryService { + constructor( + @InjectRepository(NoticeBoardHistory) + private readonly noticeBoardHistoryRepository: Repository, + ) {} + create( + entityManager: EntityManager, + userId: number, + noticeBoardId: number, + action: HistoryAction, + createNoticeBoardHistoryDto: CreateNoticeBoardHistoryDto, + ) { + return entityManager + .withRepository(this.noticeBoardHistoryRepository) + .save({ + userId, + noticeBoardId, + action, + ...new CreateNoticeBoardHistoryDto(createNoticeBoardHistoryDto), + }); + } +} diff --git a/src/apis/notice-boards/notice-boards.module.ts b/src/apis/notice-boards/notice-boards.module.ts new file mode 100644 index 00000000..ce4c64b0 --- /dev/null +++ b/src/apis/notice-boards/notice-boards.module.ts @@ -0,0 +1,14 @@ +import { Module } from '@nestjs/common'; +import { NoticeBoardsController } from './controllers/notice-boards.controller'; +import { NoticeBoardsService } from './services/notice-boards.service'; +import { TypeOrmModule } from '@nestjs/typeorm'; +import { NoticeBoard } from '@src/entities/NoticeBoard'; +import { QueryHelper } from '@src/helpers/query.helper'; +import { NoticeBoardHistoryModule } from './notice-board-history/notice-board-history.module'; + +@Module({ + imports: [TypeOrmModule.forFeature([NoticeBoard]), NoticeBoardHistoryModule], + controllers: [NoticeBoardsController], + providers: [NoticeBoardsService, QueryHelper], +}) +export class NoticeBoardsModule {} diff --git a/src/apis/notice-boards/services/notice-boards.service.ts b/src/apis/notice-boards/services/notice-boards.service.ts new file mode 100644 index 00000000..85a91a60 --- /dev/null +++ b/src/apis/notice-boards/services/notice-boards.service.ts @@ -0,0 +1,105 @@ +import { Injectable } from '@nestjs/common'; +import { CreateNoticeBoardDto } from '../dto/create-notice-board.dto'; +import { DataSource, Repository } from 'typeorm'; +import { InjectRepository } from '@nestjs/typeorm'; +import { NoticeBoard } from '@src/entities/NoticeBoard'; +import { NoticeBoardDto } from '../dto/notice-board.dto'; +import { HttpInternalServerErrorException } from '@src/http-exceptions/exceptions/http-internal-server-error.exception'; +import { COMMON_ERROR_CODE } from '@src/constants/error/common/common-error-code.constant'; +import { QueryHelper } from '@src/helpers/query.helper'; +import { FindNoticeBoardListQueryDto } from '../dto/find-notice-board-list-query.dto'; +import { NoticeBoardsItemDto } from '../dto/notice-boards-item.dto'; +import { NoticeBoardHistoryService } from '../notice-board-history/services/notice-board-history.service'; +import { HistoryAction } from '@src/constants/enum'; + +@Injectable() +export class NoticeBoardsService { + private readonly LIKE_SEARCH_FIELD: readonly (keyof Pick< + NoticeBoardDto, + 'title' + >)[] = ['title']; + + constructor( + private readonly queryHelper: QueryHelper, + private readonly noticeBoardHistoryService: NoticeBoardHistoryService, + private readonly dataSource: DataSource, + @InjectRepository(NoticeBoard) + private readonly noticeBoardRepository: Repository, + ) {} + + async create(userId: number, createNoticeBoardDto: CreateNoticeBoardDto) { + const queryRunner = this.dataSource.createQueryRunner(); + + await queryRunner.connect(); + await queryRunner.startTransaction(); + + try { + const entityManager = queryRunner.manager; + + const newPost = await entityManager + .withRepository(this.noticeBoardRepository) + .save({ + userId, + ...createNoticeBoardDto, + }); + console.log(newPost); + + await this.noticeBoardHistoryService.create( + entityManager, + newPost.userId, + newPost.id, + HistoryAction.Insert, + { + ...newPost, + }, + ); + + await queryRunner.commitTransaction(); + + return new NoticeBoardDto(newPost); + } catch (error) { + if (queryRunner.isTransactionActive) { + await queryRunner.rollbackTransaction(); + } + + console.error(error); + + throw new HttpInternalServerErrorException({ + code: COMMON_ERROR_CODE.SERVER_ERROR, + ctx: '공지게시글 생성 중 알 수 없는 에러', + stack: error.stack, + }); + } finally { + if (!queryRunner.isReleased) { + await queryRunner.release(); + } + } + } + + async findAllAndCount( + findNoticeBoardListQueryDto: FindNoticeBoardListQueryDto, + ): Promise<[NoticeBoardsItemDto[], number]> { + const { page, pageSize, order, ...filter } = findNoticeBoardListQueryDto; + + const where = this.queryHelper.buildWherePropForFind( + filter, + this.LIKE_SEARCH_FIELD, + ); + + return this.noticeBoardRepository.findAndCount({ + select: { + id: true, + userId: true, + title: true, + hit: true, + isAllowComment: true, + createdAt: true, + updatedAt: true, + }, + where, + order, + skip: page * pageSize, + take: pageSize, + }); + } +} diff --git a/src/entities/FreeBoard.ts b/src/entities/FreeBoard.ts index 2f7f872f..2ddf411e 100644 --- a/src/entities/FreeBoard.ts +++ b/src/entities/FreeBoard.ts @@ -1,6 +1,6 @@ import { FreeBoardStatus } from '@src/apis/free-boards/constants/free-board.enum'; import { FreeBoardHistory } from '@src/entities/FreeBoardHistory'; -import { BooleanTransformer } from '@src/entities/transfomers/boolean.transfomer'; +import { BooleanTransformer } from '@src/entities/transformers/boolean.transformer'; import { Column, Entity, diff --git a/src/entities/NoticeBoard.ts b/src/entities/NoticeBoard.ts index 7b1f509b..18fdf0a9 100644 --- a/src/entities/NoticeBoard.ts +++ b/src/entities/NoticeBoard.ts @@ -10,6 +10,9 @@ import { NoticeBoardComment } from './NoticeBoardComment'; import { NoticeBoardReaction } from './NoticeBoardReaction'; import { NoticeBoardReplyComment } from './NoticeBoardReplyComment'; import { User } from './User'; +import { NoticeBoardHistory } from './NoticeBoardHistory'; +import { BooleanTransformer } from './transformers/boolean.transformer'; +import { NoticeBoardStatus } from '@src/apis/notice-boards/constants/notice-board.enum'; @Entity('notice_board', { schema: 'dongurami_local_db' }) export class NoticeBoard { @@ -35,13 +38,20 @@ export class NoticeBoard { }) hit: number; - @Column('tinyint', { - name: 'allow_comment', + @Column('boolean', { + name: 'is_allow_comment', comment: '댓글 허용 여부 (0: 비활성화, 1: 허용)', - unsigned: true, - default: () => "'1'", + default: () => true, + transformer: new BooleanTransformer(), + }) + isAllowComment: boolean; + + @Column('enum', { + name: 'status', + comment: '공지게시글 상태', + enum: NoticeBoardStatus, }) - allowComment: number; + status: NoticeBoardStatus; @Column('timestamp', { name: 'created_at', @@ -57,6 +67,20 @@ export class NoticeBoard { }) updatedAt: Date; + @Column('timestamp', { + name: 'deleted_at', + nullable: true, + comment: '삭제 일자', + }) + deletedAt: Date | null; + + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + @ManyToOne(() => User, (user) => user.noticeBoards, { onDelete: 'CASCADE', onUpdate: 'CASCADE', @@ -81,4 +105,10 @@ export class NoticeBoard { (noticeBoardReplyComment) => noticeBoardReplyComment.noticeBoard, ) noticeBoardReplyComments: NoticeBoardReplyComment[]; + + @OneToMany( + () => NoticeBoardHistory, + (noticeBoardHistories) => noticeBoardHistories.noticeBoard, + ) + noticeBoardHistories: NoticeBoardHistory[]; } diff --git a/src/entities/NoticeBoardComment.ts b/src/entities/NoticeBoardComment.ts index 5f53bff9..ee962cd7 100644 --- a/src/entities/NoticeBoardComment.ts +++ b/src/entities/NoticeBoardComment.ts @@ -10,6 +10,7 @@ import { NoticeBoard } from './NoticeBoard'; import { NoticeBoardCommentReaction } from './NoticeBoardCommentReaction'; import { NoticeBoardReplyComment } from './NoticeBoardReplyComment'; import { User } from './User'; +import { BooleanTransformer } from './transformers/boolean.transformer'; @Entity('notice_board_comment', { schema: 'dongurami_local_db' }) export class NoticeBoardComment { @@ -24,13 +25,13 @@ export class NoticeBoardComment { @Column('varchar', { name: 'description', comment: '댓글 본문', length: 255 }) description: string; - @Column('tinyint', { - name: 'isAnonymous', + @Column('boolean', { + name: 'is_anonymous', comment: '작성자 익명 여부 (0: 실명, 1: 익명)', - unsigned: true, - default: () => "'0'", + default: () => false, + transformer: new BooleanTransformer(), }) - isAnonymous: number; + isAnonymous: boolean; @Column('timestamp', { name: 'created_at', @@ -46,6 +47,13 @@ export class NoticeBoardComment { }) updatedAt: Date; + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + @ManyToOne(() => User, (user) => user.noticeBoardComments, { onDelete: 'CASCADE', onUpdate: 'CASCADE', @@ -53,6 +61,13 @@ export class NoticeBoardComment { @JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }]) user: User; + @Column('int', { + name: 'notice_board_id', + comment: '공지 게시글 고유 ID', + unsigned: true, + }) + noticeBoardId: number; + @ManyToOne( () => NoticeBoard, (noticeBoard) => noticeBoard.noticeBoardComments, diff --git a/src/entities/NoticeBoardCommentHistory.ts b/src/entities/NoticeBoardCommentHistory.ts new file mode 100644 index 00000000..307da064 --- /dev/null +++ b/src/entities/NoticeBoardCommentHistory.ts @@ -0,0 +1,63 @@ +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { NoticeBoardHistory } from './NoticeBoardHistory'; +import { NoticeBoardReplyCommentHistory } from './NoticeBoardReplyCommentHistory'; +import { User } from './User'; +import { BooleanTransformer } from './transformers/boolean.transformer'; + +@Entity('notice_board_comment_history', { schema: 'dongurami_local_db' }) +export class NoticeBoardCommentHistory { + @PrimaryGeneratedColumn({ + type: 'int', + name: 'id', + comment: '공지게시글 댓글 수정이력 고유 ID', + unsigned: true, + }) + id: number; + + @Column('varchar', { name: 'description', comment: '댓글 본문', length: 255 }) + description: string; + + @Column('boolean', { + name: 'is_anonymous', + comment: '작성자 익명 여부 (0: 실명, 1: 익명)', + default: () => false, + transformer: new BooleanTransformer(), + }) + isAnonymous: boolean; + + @Column('timestamp', { + name: 'created_at', + comment: '생성 일자', + default: () => 'CURRENT_TIMESTAMP', + }) + createdAt: Date; + + @ManyToOne(() => User, (user) => user.noticeBoardCommentHistories, { + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }) + @JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }]) + user: User; + + @ManyToOne( + () => NoticeBoardHistory, + (noticeBoardHistory) => noticeBoardHistory.noticeBoardCommentHistories, + { onDelete: 'NO ACTION', onUpdate: 'NO ACTION' }, + ) + @JoinColumn([{ name: 'notice_board_history_id', referencedColumnName: 'id' }]) + noticeBoardHistory: NoticeBoardHistory; + + @OneToMany( + () => NoticeBoardReplyCommentHistory, + (noticeBoardReplyCommentHistory) => + noticeBoardReplyCommentHistory.noticeBoardCommentHistory, + ) + noticeBoardReplyCommentHistories: NoticeBoardReplyCommentHistory[]; +} diff --git a/src/entities/NoticeBoardCommentReaction.ts b/src/entities/NoticeBoardCommentReaction.ts index 3ba9801d..efcdb99d 100644 --- a/src/entities/NoticeBoardCommentReaction.ts +++ b/src/entities/NoticeBoardCommentReaction.ts @@ -26,6 +26,13 @@ export class NoticeBoardCommentReaction { }) createdAt: Date; + @Column('int', { + name: 'notice_board_comment_id', + comment: '공지 게시글 댓글 고유 ID', + unsigned: true, + }) + noticeBoardCommentId: number; + @ManyToOne( () => NoticeBoardComment, (noticeBoardComment) => noticeBoardComment.noticeBoardCommentReactions, @@ -34,6 +41,13 @@ export class NoticeBoardCommentReaction { @JoinColumn([{ name: 'notice_board_comment_id', referencedColumnName: 'id' }]) noticeBoardComment: NoticeBoardComment; + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + @ManyToOne(() => User, (user) => user.noticeBoardCommentReactions, { onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/entities/NoticeBoardHistory.ts b/src/entities/NoticeBoardHistory.ts new file mode 100644 index 00000000..5d3a795b --- /dev/null +++ b/src/entities/NoticeBoardHistory.ts @@ -0,0 +1,103 @@ +import { + Column, + Entity, + JoinColumn, + ManyToOne, + OneToMany, + PrimaryGeneratedColumn, +} from 'typeorm'; +import { NoticeBoardCommentHistory } from './NoticeBoardCommentHistory'; +import { NoticeBoardReplyCommentHistory } from './NoticeBoardReplyCommentHistory'; +import { User } from './User'; +import { NoticeBoard } from './NoticeBoard'; +import { BooleanTransformer } from './transformers/boolean.transformer'; +import { HistoryAction } from '@src/constants/enum'; +import { NoticeBoardStatus } from '@src/apis/notice-boards/constants/notice-board.enum'; + +@Entity('notice_board_history', { schema: 'dongurami_local_db' }) +export class NoticeBoardHistory { + @PrimaryGeneratedColumn({ + type: 'int', + name: 'id', + comment: '공지 게시글 히스토리 고유 ID', + unsigned: true, + }) + id: number; + + @Column('varchar', { name: 'title', comment: '공지게시글 제목', length: 255 }) + title: string; + + @Column('text', { name: 'description', comment: '공지게시글 내용' }) + description: string; + + @Column('boolean', { + name: 'is_allow_comment', + comment: '댓글 허용 여부 (0: 비활성화, 1: 허용)', + default: () => true, + transformer: new BooleanTransformer(), + }) + isAllowComment: boolean; + + @Column('enum', { + name: 'action', + comment: 'history 를 쌓는 action', + enum: HistoryAction, + }) + action: HistoryAction; + + @Column('enum', { + name: 'status', + comment: '공지게시글 상태', + enum: NoticeBoardStatus, + }) + status: NoticeBoardStatus; + + @Column('timestamp', { + name: 'created_at', + comment: '생성 일자', + default: () => 'CURRENT_TIMESTAMP', + }) + createdAt: Date; + + @OneToMany( + () => NoticeBoardCommentHistory, + (noticeBoardCommentHistory) => noticeBoardCommentHistory.noticeBoardHistory, + ) + noticeBoardCommentHistories: NoticeBoardCommentHistory[]; + + @Column('int', { + name: 'notice_board_id', + comment: '공지 게시글 고유 ID', + unsigned: true, + }) + noticeBoardId: number; + + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + + @ManyToOne(() => User, (user) => user.noticeBoardHistories, { + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }) + @JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }]) + user: User; + + @ManyToOne( + () => NoticeBoard, + (noticeBoard) => noticeBoard.noticeBoardHistories, + { onDelete: 'NO ACTION', onUpdate: 'NO ACTION' }, + ) + @JoinColumn([{ name: 'notice_board_id', referencedColumnName: 'id' }]) + noticeBoard: NoticeBoard; + + @OneToMany( + () => NoticeBoardReplyCommentHistory, + (noticeBoardReplyCommentHistory) => + noticeBoardReplyCommentHistory.noticeBoardHistory, + ) + noticeBoardReplyCommentHistories: NoticeBoardReplyCommentHistory[]; +} diff --git a/src/entities/NoticeBoardReaction.ts b/src/entities/NoticeBoardReaction.ts index 97a5aa7c..c05c9bd3 100644 --- a/src/entities/NoticeBoardReaction.ts +++ b/src/entities/NoticeBoardReaction.ts @@ -26,6 +26,13 @@ export class NoticeBoardReaction { }) createdAt: Date; + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + @ManyToOne(() => User, (user) => user.noticeBoardReactions, { onDelete: 'CASCADE', onUpdate: 'CASCADE', @@ -33,6 +40,13 @@ export class NoticeBoardReaction { @JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }]) user: User; + @Column('int', { + name: 'notice_board_id', + comment: '공지 게시글 고유 ID', + unsigned: true, + }) + noticeBoardId: number; + @ManyToOne( () => NoticeBoard, (noticeBoard) => noticeBoard.noticeBoardReactions, diff --git a/src/entities/NoticeBoardReplyComment.ts b/src/entities/NoticeBoardReplyComment.ts index e2503c86..1bbe49df 100644 --- a/src/entities/NoticeBoardReplyComment.ts +++ b/src/entities/NoticeBoardReplyComment.ts @@ -28,13 +28,12 @@ export class NoticeBoardReplyComment { }) description: string; - @Column('tinyint', { - name: 'isAnonymous', + @Column('boolean', { + name: 'is_anonymous', comment: '작성자 익명 여부 (0: 실명, 1: 익명)', - unsigned: true, - default: () => "'0'", + default: () => false, }) - isAnonymous: number; + isAnonymous: boolean; @Column('timestamp', { name: 'created_at', @@ -50,6 +49,13 @@ export class NoticeBoardReplyComment { }) updatedAt: Date; + @Column('int', { + name: 'notice_board_id', + comment: '공지 게시글 고유 ID', + unsigned: true, + }) + noticeBoardId: number; + @ManyToOne( () => NoticeBoard, (noticeBoard) => noticeBoard.noticeBoardReplyComments, @@ -58,6 +64,13 @@ export class NoticeBoardReplyComment { @JoinColumn([{ name: 'notice_board_id', referencedColumnName: 'id' }]) noticeBoard: NoticeBoard; + @Column('int', { + name: 'notice_board_comment_id', + comment: '공지 게시글 댓글 고유 ID', + unsigned: true, + }) + noticeBoardCommentId: number; + @ManyToOne( () => NoticeBoardComment, (noticeBoardComment) => noticeBoardComment.noticeBoardReplyComments, @@ -66,6 +79,13 @@ export class NoticeBoardReplyComment { @JoinColumn([{ name: 'notice_board_comment_id', referencedColumnName: 'id' }]) noticeBoardComment: NoticeBoardComment; + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + @ManyToOne(() => User, (user) => user.noticeBoardReplyComments, { onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/entities/NoticeBoardReplyCommentHistory.ts b/src/entities/NoticeBoardReplyCommentHistory.ts new file mode 100644 index 00000000..01eed198 --- /dev/null +++ b/src/entities/NoticeBoardReplyCommentHistory.ts @@ -0,0 +1,71 @@ +import { + Column, + Entity, + JoinColumn, + ManyToOne, + PrimaryGeneratedColumn, +} from 'typeorm'; + +import { NoticeBoardCommentHistory } from './NoticeBoardCommentHistory'; +import { NoticeBoardHistory } from './NoticeBoardHistory'; +import { User } from './User'; +import { BooleanTransformer } from './transformers/boolean.transformer'; + +@Entity('notice_board_reply_comment_history', { schema: 'dongurami_local_db' }) +export class NoticeBoardReplyCommentHistory { + @PrimaryGeneratedColumn({ + type: 'int', + name: 'id', + comment: '공지 게시글 대댓글 수정이력 고유 ID', + unsigned: true, + }) + id: number; + + @Column('varchar', { + name: 'description', + comment: '대댓글 본문', + length: 255, + }) + description: string; + + @Column('boolean', { + name: 'is_anonymous', + comment: '작성자 익명 여부 (0: 실명, 1: 익명)', + default: () => false, + transformer: new BooleanTransformer(), + }) + isAnonymous: boolean; + + @Column('timestamp', { + name: 'created_at', + comment: '생성 일자', + default: () => 'CURRENT_TIMESTAMP', + }) + createdAt: Date; + + @ManyToOne(() => User, (user) => user.noticeBoardReplyCommentHistories, { + onDelete: 'NO ACTION', + onUpdate: 'NO ACTION', + }) + @JoinColumn([{ name: 'user_id', referencedColumnName: 'id' }]) + user: User; + + @ManyToOne( + () => NoticeBoardCommentHistory, + (noticeBoardCommentHistory) => + noticeBoardCommentHistory.noticeBoardReplyCommentHistories, + { onDelete: 'NO ACTION', onUpdate: 'NO ACTION' }, + ) + @JoinColumn([ + { name: 'notice_board_comment_history_id', referencedColumnName: 'id' }, + ]) + noticeBoardCommentHistory: NoticeBoardCommentHistory; + + @ManyToOne( + () => NoticeBoardHistory, + (noticeBoardHistory) => noticeBoardHistory.noticeBoardReplyCommentHistories, + { onDelete: 'NO ACTION', onUpdate: 'NO ACTION' }, + ) + @JoinColumn([{ name: 'notice_board_history_id', referencedColumnName: 'id' }]) + noticeBoardHistory: NoticeBoardHistory; +} diff --git a/src/entities/NoticeBoardReplyCommentReaction.ts b/src/entities/NoticeBoardReplyCommentReaction.ts index 3895edf7..54f1e847 100644 --- a/src/entities/NoticeBoardReplyCommentReaction.ts +++ b/src/entities/NoticeBoardReplyCommentReaction.ts @@ -34,6 +34,13 @@ export class NoticeBoardReplyCommentReaction { @JoinColumn([{ name: 'reaction_type_id', referencedColumnName: 'id' }]) reactionType: ReactionType; + @Column('int', { + name: 'notice_board_reply_comment_id', + comment: '공지 게시글 대댓글 고유 ID', + unsigned: true, + }) + noticeBoardReplyCommentId: number; + @ManyToOne( () => NoticeBoardReplyComment, (noticeBoardReplyComment) => @@ -45,6 +52,13 @@ export class NoticeBoardReplyCommentReaction { ]) noticeBoardReplyComment: NoticeBoardReplyComment; + @Column('int', { + name: 'user_id', + comment: '게시글 작성 유저 고유 ID', + unsigned: true, + }) + userId: number; + @ManyToOne(() => User, (user) => user.noticeBoardReplyCommentReactions, { onDelete: 'CASCADE', onUpdate: 'CASCADE', diff --git a/src/entities/User.ts b/src/entities/User.ts index 205231dc..a2f54b69 100644 --- a/src/entities/User.ts +++ b/src/entities/User.ts @@ -31,6 +31,9 @@ import { NoticeBoardCommentReaction } from './NoticeBoardCommentReaction'; import { NoticeBoardReaction } from './NoticeBoardReaction'; import { NoticeBoardReplyComment } from './NoticeBoardReplyComment'; import { NoticeBoardReplyCommentReaction } from './NoticeBoardReplyCommentReaction'; +import { NoticeBoardHistory } from './NoticeBoardHistory'; +import { NoticeBoardCommentHistory } from './NoticeBoardCommentHistory'; +import { NoticeBoardReplyCommentHistory } from './NoticeBoardReplyCommentHistory'; @Entity('user', { schema: 'dongurami_v2' }) export class User { @@ -201,12 +204,24 @@ export class User { @OneToMany(() => NoticeBoard, (noticeBoard) => noticeBoard.user) noticeBoards: NoticeBoard[]; + @OneToMany( + () => NoticeBoardHistory, + (noticeBoardHistories) => noticeBoardHistories.user, + ) + noticeBoardHistories: NoticeBoardHistory[]; + @OneToMany( () => NoticeBoardComment, (noticeBoardComment) => noticeBoardComment.user, ) noticeBoardComments: NoticeBoardComment[]; + @OneToMany( + () => NoticeBoardCommentHistory, + (noticeBoardCommentHistories) => noticeBoardCommentHistories.user, + ) + noticeBoardCommentHistories: NoticeBoardCommentHistory[]; + @OneToMany( () => NoticeBoardCommentReaction, (noticeBoardCommentReaction) => noticeBoardCommentReaction.user, @@ -225,6 +240,12 @@ export class User { ) noticeBoardReplyComments: NoticeBoardReplyComment[]; + @OneToMany( + () => NoticeBoardReplyCommentHistory, + (noticeBoardReplyCommentHistories) => noticeBoardReplyCommentHistories.user, + ) + noticeBoardReplyCommentHistories: NoticeBoardReplyCommentHistory[]; + @OneToMany( () => NoticeBoardReplyCommentReaction, (noticeBoardReplyCommentReaction) => noticeBoardReplyCommentReaction.user, diff --git a/src/entities/transfomers/boolean.transfomer.ts b/src/entities/transformers/boolean.transformer.ts similarity index 100% rename from src/entities/transfomers/boolean.transfomer.ts rename to src/entities/transformers/boolean.transformer.ts