From d2994596f86d734d8d59285a7d313a9c8dd4fb9b Mon Sep 17 00:00:00 2001 From: chacha Date: Tue, 19 Nov 2024 04:42:17 +0900 Subject: [PATCH 1/8] feat: impl mee006 --- .../src/feature/meeting/meeting.controller.ts | 34 ++++++++++- .../src/feature/meeting/meeting.repository.ts | 39 ++++++++++++- .../src/feature/meeting/meeting.service.ts | 27 +++++++++ .../interface/src/api/meeting/apiMee006.ts | 57 +++++++++++++++++++ 4 files changed, 155 insertions(+), 2 deletions(-) create mode 100644 packages/interface/src/api/meeting/apiMee006.ts diff --git a/packages/api/src/feature/meeting/meeting.controller.ts b/packages/api/src/feature/meeting/meeting.controller.ts index 7423bdd43..5a348cb91 100644 --- a/packages/api/src/feature/meeting/meeting.controller.ts +++ b/packages/api/src/feature/meeting/meeting.controller.ts @@ -1,10 +1,24 @@ -import { Controller, Get, Query, UsePipes } from "@nestjs/common"; +import { + Body, + Controller, + Get, + Param, + Post, + Query, + UsePipes, +} from "@nestjs/common"; import apiMee005, { ApiMee005RequestQuery, ApiMee005ResponseOk, } from "@sparcs-clubs/interface/api/meeting/apiMee005"; +import apiMee006, { + ApiMee006RequestBody, + ApiMee006RequestParam, + ApiMee006ResponseCreated, +} from "@sparcs-clubs/interface/api/meeting/apiMee006"; + import { ZodPipe } from "@sparcs-clubs/api/common/pipe/zod-pipe"; import { Executive } from "@sparcs-clubs/api/common/util/decorators/method-decorator"; import { GetExecutive } from "@sparcs-clubs/api/common/util/decorators/param-decorator"; @@ -29,4 +43,22 @@ export default class MeetingController { return { degree }; } + + @Executive() + @Post("/executive/meetings/meeting/:meetingId/agendas/agenda") + @UsePipes(new ZodPipe(apiMee006)) + async postStudentRegistrationsMemberRegistration( + @GetExecutive() user: GetExecutive, + @Param() { meetingId }: ApiMee006RequestParam, + @Body() { description, meetingEnumId, title }: ApiMee006RequestBody, + ): Promise { + const result = await this.meetingService.postExecutiveMeetingAgenda( + user.executiveId, + meetingId, + description, + meetingEnumId, + title, + ); + return result; + } } diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index 95f69f216..0d1015951 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -1,14 +1,16 @@ import { Inject, Injectable } from "@nestjs/common"; -import { and, eq, gte, lt } from "drizzle-orm"; +import { and, eq, gte, lt, max } from "drizzle-orm"; import { MySql2Database } from "drizzle-orm/mysql2"; import logger from "@sparcs-clubs/api/common/util/logger"; import { getKSTDate } from "@sparcs-clubs/api/common/util/util"; import { Meeting, + MeetingAgenda, MeetingAnnouncement, MeetingAttendanceTimeT, + MeetingMapping, MeetingVoteResult, } from "@sparcs-clubs/api/drizzle/schema/meeting.schema"; @@ -307,4 +309,39 @@ export class MeetingRepository { return result.length; } + + async entryMeetingAgenda( + meetingEnumId: number, + description: string, + title: string, + ) { + const [result] = await this.db.insert(MeetingAgenda).values({ + MeetingAgendaEnum: meetingEnumId, + description, + title, + isEditableSelf: true, + isEditableDivisionPresident: true, + isEditableRepresentative: true, + }); + + return result.insertId; + } + + async entryMeetingMapping(agendaId: number, meetingId: number) { + const getMax = await this.db + .select({ value: max(MeetingMapping.meetingAgendaPosition) }) + .from(MeetingMapping) + .where(and(eq(MeetingMapping.meetingId, meetingId))); + + const maxAgendaPosition = getMax[0]?.value; + + const [result] = await this.db.insert(MeetingMapping).values({ + meetingId, + meetingAgendaId: agendaId, + meetingAgendaPosition: maxAgendaPosition + 1, + meetingAgendaEntityType: 3, // no agenda entity mapped yet. + }); + + return result; + } } diff --git a/packages/api/src/feature/meeting/meeting.service.ts b/packages/api/src/feature/meeting/meeting.service.ts index 4b23557b6..4e5f2e3ca 100644 --- a/packages/api/src/feature/meeting/meeting.service.ts +++ b/packages/api/src/feature/meeting/meeting.service.ts @@ -2,6 +2,8 @@ import { HttpException, HttpStatus, Injectable } from "@nestjs/common"; import { WsException } from "@nestjs/websockets"; +import { ApiMee006ResponseCreated } from "@sparcs-clubs/interface/api/meeting/apiMee006"; + import UserPublicService from "../user/service/user.public.service"; import { MeetingRepository } from "./meeting.repository"; @@ -55,4 +57,29 @@ export class MeetingService { return result; } + + async postExecutiveMeetingAgenda( + executiveId: number, + meetingId: number, + description: string, + meetingEnumId: number, + title: string, + ): Promise { + const user = await this.userPublicService.getExecutiveById({ + id: executiveId, + }); + + if (!user) { + throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); + } + + const agendaId = await this.meetingRepository.entryMeetingAgenda( + meetingEnumId, + description, + title, + ); + await this.meetingRepository.entryMeetingMapping(agendaId, meetingId); + + return {}; + } } diff --git a/packages/interface/src/api/meeting/apiMee006.ts b/packages/interface/src/api/meeting/apiMee006.ts new file mode 100644 index 000000000..77d09abc6 --- /dev/null +++ b/packages/interface/src/api/meeting/apiMee006.ts @@ -0,0 +1,57 @@ +import { HttpStatusCode } from "axios"; +import { z } from "zod"; + +import { MeetingEnum } from "@sparcs-clubs/interface/common/enum/meeting.enum"; + +/** + * @version v0.1 + * @description 새로운 MeetingAgenda를 생성하고, Mapping Table에 연결합니다. + */ + +const url = (meetingId: number) => + `/executive/meetings/meeting/${meetingId}/agendas/agenda`; +const method = "POST"; + +const requestParam = z.object({ + meetingId: z.coerce.number().int().min(1), +}); + +const requestQuery = z.object({}); + +const requestBody = z.object({ + meetingEnumId: z.nativeEnum(MeetingEnum), + title: z.coerce.string().max(255), + description: z.coerce.string(), +}); + +const responseBodyMap = { + [HttpStatusCode.Created]: z.object({}), +}; + +const responseErrorMap = {}; + +const apiMee006 = { + url, + method, + requestParam, + requestQuery, + requestBody, + responseBodyMap, + responseErrorMap, +}; + +type ApiMee006RequestParam = z.infer; +type ApiMee006RequestQuery = z.infer; +type ApiMee006RequestBody = z.infer; +type ApiMee006ResponseCreated = z.infer< + (typeof apiMee006.responseBodyMap)[201] +>; + +export default apiMee006; + +export type { + ApiMee006RequestParam, + ApiMee006RequestQuery, + ApiMee006RequestBody, + ApiMee006ResponseCreated, +}; From 3b2942e4944f67457180b987de0adc0895db8dc0 Mon Sep 17 00:00:00 2001 From: chacha Date: Tue, 19 Nov 2024 19:43:52 +0900 Subject: [PATCH 2/8] feat: impl mee008 --- .../src/feature/meeting/meeting.controller.ts | 31 +++++++++-- .../src/feature/meeting/meeting.repository.ts | 19 +++++++ .../src/feature/meeting/meeting.service.ts | 27 +++++++++- .../interface/src/api/meeting/apiMee008.ts | 54 +++++++++++++++++++ 4 files changed, 127 insertions(+), 4 deletions(-) create mode 100644 packages/interface/src/api/meeting/apiMee008.ts diff --git a/packages/api/src/feature/meeting/meeting.controller.ts b/packages/api/src/feature/meeting/meeting.controller.ts index 5a348cb91..3edf8c17e 100644 --- a/packages/api/src/feature/meeting/meeting.controller.ts +++ b/packages/api/src/feature/meeting/meeting.controller.ts @@ -3,6 +3,7 @@ import { Controller, Get, Param, + Patch, Post, Query, UsePipes, @@ -19,6 +20,12 @@ import apiMee006, { ApiMee006ResponseCreated, } from "@sparcs-clubs/interface/api/meeting/apiMee006"; +import apiMee08, { + ApiMee008RequestBody, + ApiMee008RequestParam, + ApiMee008ResponseOk, +} from "@sparcs-clubs/interface/api/meeting/apiMee008"; + import { ZodPipe } from "@sparcs-clubs/api/common/pipe/zod-pipe"; import { Executive } from "@sparcs-clubs/api/common/util/decorators/method-decorator"; import { GetExecutive } from "@sparcs-clubs/api/common/util/decorators/param-decorator"; @@ -47,16 +54,34 @@ export default class MeetingController { @Executive() @Post("/executive/meetings/meeting/:meetingId/agendas/agenda") @UsePipes(new ZodPipe(apiMee006)) - async postStudentRegistrationsMemberRegistration( + async postExecutiveMeetingAgenda( @GetExecutive() user: GetExecutive, @Param() { meetingId }: ApiMee006RequestParam, - @Body() { description, meetingEnumId, title }: ApiMee006RequestBody, + @Body() { meetingEnumId, description, title }: ApiMee006RequestBody, ): Promise { const result = await this.meetingService.postExecutiveMeetingAgenda( user.executiveId, meetingId, - description, meetingEnumId, + description, + title, + ); + return result; + } + + @Executive() + @Patch("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") + @UsePipes(new ZodPipe(apiMee08)) + async patchExecutiveMeetingAgenda( + @GetExecutive() user: GetExecutive, + @Param() { agendaId }: ApiMee008RequestParam, + @Body() { agendaEnumId, description, title }: ApiMee008RequestBody, + ): Promise { + const result = await this.meetingService.patchExecutiveMeetingAgenda( + user.executiveId, + agendaId, + agendaEnumId, + description, title, ); return result; diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index 0d1015951..c6a0fb3dd 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -344,4 +344,23 @@ export class MeetingRepository { return result; } + + async updateMeetingAgenda( + agendaId: number, + agendaEnumId: number, + description: string, + title: string, + ) { + const updateTime = new Date(); + const [result] = await this.db + .update(MeetingAgenda) + .set({ + MeetingAgendaEnum: agendaEnumId, + title, + description, + updatedAt: updateTime, + }) + .where(eq(MeetingAgenda.id, agendaId)); + return result; + } } diff --git a/packages/api/src/feature/meeting/meeting.service.ts b/packages/api/src/feature/meeting/meeting.service.ts index 4e5f2e3ca..b1251ec2d 100644 --- a/packages/api/src/feature/meeting/meeting.service.ts +++ b/packages/api/src/feature/meeting/meeting.service.ts @@ -61,8 +61,8 @@ export class MeetingService { async postExecutiveMeetingAgenda( executiveId: number, meetingId: number, - description: string, meetingEnumId: number, + description: string, title: string, ): Promise { const user = await this.userPublicService.getExecutiveById({ @@ -82,4 +82,29 @@ export class MeetingService { return {}; } + + async patchExecutiveMeetingAgenda( + executiveId: number, + agendaId: number, + agendaEnumId: number, + description: string, + title: string, + ) { + const user = await this.userPublicService.getExecutiveById({ + id: executiveId, + }); + + if (!user) { + throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); + } + + await this.meetingRepository.updateMeetingAgenda( + agendaId, + agendaEnumId, + description, + title, + ); + + return {}; + } } diff --git a/packages/interface/src/api/meeting/apiMee008.ts b/packages/interface/src/api/meeting/apiMee008.ts new file mode 100644 index 000000000..53c9df7ba --- /dev/null +++ b/packages/interface/src/api/meeting/apiMee008.ts @@ -0,0 +1,54 @@ +import { HttpStatusCode } from "axios"; +import { z } from "zod"; + +/** + * @version v0.1 + * @description 단일 agenda에 대한 내용 수정을 진행합니다. + */ + +const url = (meetingId: number, agendaId: number) => + `/executive/meetings/meeting/${meetingId}/agendas/agenda/${agendaId}`; +const method = "PATCH"; + +const requestParam = z.object({ + meetingId: z.coerce.number().int().min(1), + agendaId: z.coerce.number().int().min(1), +}); + +const requestQuery = z.object({}); + +const requestBody = z.object({ + agendaEnumId: z.coerce.number().int().min(1), + title: z.coerce.string().max(255), + description: z.coerce.string(), +}); + +const responseBodyMap = { + [HttpStatusCode.Ok]: z.object({}), +}; + +const responseErrorMap = {}; + +const apiMee008 = { + url, + method, + requestParam, + requestQuery, + requestBody, + responseBodyMap, + responseErrorMap, +}; + +type ApiMee008RequestParam = z.infer; +type ApiMee008RequestQuery = z.infer; +type ApiMee008RequestBody = z.infer; +type ApiMee008ResponseOk = z.infer<(typeof apiMee008.responseBodyMap)[200]>; + +export default apiMee008; + +export type { + ApiMee008RequestParam, + ApiMee008RequestQuery, + ApiMee008RequestBody, + ApiMee008ResponseOk, +}; From 56ee88b7b4e63f9be40048f26a98651e5798ba56 Mon Sep 17 00:00:00 2001 From: chacha Date: Tue, 19 Nov 2024 20:23:42 +0900 Subject: [PATCH 3/8] feat: impl mee010 --- .../src/feature/meeting/meeting.controller.ts | 25 ++++- .../src/feature/meeting/meeting.repository.ts | 102 +++++++++++++----- .../src/feature/meeting/meeting.service.ts | 31 +++++- .../interface/src/api/meeting/apiMee010.ts | 50 +++++++++ 4 files changed, 179 insertions(+), 29 deletions(-) create mode 100644 packages/interface/src/api/meeting/apiMee010.ts diff --git a/packages/api/src/feature/meeting/meeting.controller.ts b/packages/api/src/feature/meeting/meeting.controller.ts index 3edf8c17e..2bf496c8a 100644 --- a/packages/api/src/feature/meeting/meeting.controller.ts +++ b/packages/api/src/feature/meeting/meeting.controller.ts @@ -1,6 +1,7 @@ import { Body, Controller, + Delete, Get, Param, Patch, @@ -20,12 +21,17 @@ import apiMee006, { ApiMee006ResponseCreated, } from "@sparcs-clubs/interface/api/meeting/apiMee006"; -import apiMee08, { +import apiMee008, { ApiMee008RequestBody, ApiMee008RequestParam, ApiMee008ResponseOk, } from "@sparcs-clubs/interface/api/meeting/apiMee008"; +import apiMee010, { + ApiMee010RequestParam, + ApiMee010ResponseOk, +} from "@sparcs-clubs/interface/api/meeting/apiMee010"; + import { ZodPipe } from "@sparcs-clubs/api/common/pipe/zod-pipe"; import { Executive } from "@sparcs-clubs/api/common/util/decorators/method-decorator"; import { GetExecutive } from "@sparcs-clubs/api/common/util/decorators/param-decorator"; @@ -71,7 +77,7 @@ export default class MeetingController { @Executive() @Patch("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") - @UsePipes(new ZodPipe(apiMee08)) + @UsePipes(new ZodPipe(apiMee008)) async patchExecutiveMeetingAgenda( @GetExecutive() user: GetExecutive, @Param() { agendaId }: ApiMee008RequestParam, @@ -86,4 +92,19 @@ export default class MeetingController { ); return result; } + + @Executive() + @Delete("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") + @UsePipes(new ZodPipe(apiMee010)) + async deleteExecutiveMeetingAgenda( + @GetExecutive() user: GetExecutive, + @Param() { meetingId, agendaId }: ApiMee010RequestParam, + ): Promise { + const result = await this.meetingService.deleteExecutiveMeetingAgenda( + user.executiveId, + meetingId, + agendaId, + ); + return result; + } } diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index c6a0fb3dd..163590b3f 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -310,39 +310,66 @@ export class MeetingRepository { return result.length; } - async entryMeetingAgenda( + async insertMeetingAgendaAndMapping( + meetingId: number, meetingEnumId: number, description: string, title: string, ) { - const [result] = await this.db.insert(MeetingAgenda).values({ - MeetingAgendaEnum: meetingEnumId, - description, - title, - isEditableSelf: true, - isEditableDivisionPresident: true, - isEditableRepresentative: true, - }); + const isInsertAgendaAndMappingSuccess = await this.db.transaction( + async tx => { + const [insertAgendaResult] = await tx.insert(MeetingAgenda).values({ + MeetingAgendaEnum: meetingEnumId, + description, + title, + isEditableSelf: true, + isEditableDivisionPresident: true, + isEditableRepresentative: true, + }); - return result.insertId; - } + if (insertAgendaResult.affectedRows !== 1) { + logger.debug("[MeetingRepository] Failed to insert meeting agenda"); + tx.rollback(); + return false; + } + + const agendaId = insertAgendaResult.insertId; + logger.debug( + `[MeetingRepository] Inserted meeting agenda: ${agendaId}`, + ); - async entryMeetingMapping(agendaId: number, meetingId: number) { - const getMax = await this.db - .select({ value: max(MeetingMapping.meetingAgendaPosition) }) - .from(MeetingMapping) - .where(and(eq(MeetingMapping.meetingId, meetingId))); + const getMax = await tx + .select({ value: max(MeetingMapping.meetingAgendaPosition) }) + .from(MeetingMapping) + .where(and(eq(MeetingMapping.meetingId, meetingId))); - const maxAgendaPosition = getMax[0]?.value; + const maxAgendaPosition = getMax[0]?.value; - const [result] = await this.db.insert(MeetingMapping).values({ - meetingId, - meetingAgendaId: agendaId, - meetingAgendaPosition: maxAgendaPosition + 1, - meetingAgendaEntityType: 3, // no agenda entity mapped yet. - }); + const [insertMappingResult] = await this.db + .insert(MeetingMapping) + .values({ + meetingId, + meetingAgendaId: agendaId, + meetingAgendaPosition: maxAgendaPosition + 1, + meetingAgendaEntityType: 3, // no agenda entity mapped yet. + }); - return result; + if (insertMappingResult.affectedRows !== 1) { + logger.debug("[MeetingRepository] Failed to insert meeting agenda"); + tx.rollback(); + return false; + } + + const meetingMappingId = insertMappingResult.insertId; + logger.debug( + `[MeetingRepository] Inserted meeting agenda mapping: ${meetingMappingId}`, + ); + + return true; + }, + ); + + return isInsertAgendaAndMappingSuccess; } async updateMeetingAgenda( @@ -361,6 +388,33 @@ export class MeetingRepository { updatedAt: updateTime, }) .where(eq(MeetingAgenda.id, agendaId)); + + if (result.affectedRows !== 1) { + logger.debug("[MeetingRepository] Failed to update meeting agenda."); + return false; + } + logger.debug(`[MeetingRepository] Updated meeting agenda: ${agendaId}`); return result; } + + async deleteMeetingAgendaMapping(meetingId: number, agendaId: number) { + const [meetingAgendaMappingDeleteResult] = await this.db + .delete(MeetingMapping) + .where( + and( + eq(MeetingMapping.meetingId, meetingId), + eq(MeetingMapping.meetingAgendaId, agendaId), + ), + ); + if (meetingAgendaMappingDeleteResult.affectedRows !== 1) { + logger.debug( + "[MeetingRepository] Failed to delete meeting agenda mapping.", + ); + return false; + } + logger.debug( + `[MeetingRepository] Deleted meeting agenda mapping: ${meetingId}, ${agendaId}`, + ); + return meetingAgendaMappingDeleteResult; + } } diff --git a/packages/api/src/feature/meeting/meeting.service.ts b/packages/api/src/feature/meeting/meeting.service.ts index b1251ec2d..da5869096 100644 --- a/packages/api/src/feature/meeting/meeting.service.ts +++ b/packages/api/src/feature/meeting/meeting.service.ts @@ -4,6 +4,10 @@ import { WsException } from "@nestjs/websockets"; import { ApiMee006ResponseCreated } from "@sparcs-clubs/interface/api/meeting/apiMee006"; +import { ApiMee008ResponseOk } from "@sparcs-clubs/interface/api/meeting/apiMee008"; + +import { ApiMee010ResponseOk } from "@sparcs-clubs/interface/api/meeting/apiMee010"; + import UserPublicService from "../user/service/user.public.service"; import { MeetingRepository } from "./meeting.repository"; @@ -73,12 +77,12 @@ export class MeetingService { throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); } - const agendaId = await this.meetingRepository.entryMeetingAgenda( + await this.meetingRepository.insertMeetingAgendaAndMapping( + meetingId, meetingEnumId, description, title, ); - await this.meetingRepository.entryMeetingMapping(agendaId, meetingId); return {}; } @@ -89,7 +93,7 @@ export class MeetingService { agendaEnumId: number, description: string, title: string, - ) { + ): Promise { const user = await this.userPublicService.getExecutiveById({ id: executiveId, }); @@ -107,4 +111,25 @@ export class MeetingService { return {}; } + + async deleteExecutiveMeetingAgenda( + executiveId: number, + meetingId: number, + agendaId: number, + ): Promise { + const user = await this.userPublicService.getExecutiveById({ + id: executiveId, + }); + + if (!user) { + throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); + } + + await this.meetingRepository.deleteMeetingAgendaMapping( + meetingId, + agendaId, + ); + + return {}; + } } diff --git a/packages/interface/src/api/meeting/apiMee010.ts b/packages/interface/src/api/meeting/apiMee010.ts new file mode 100644 index 000000000..5010279cd --- /dev/null +++ b/packages/interface/src/api/meeting/apiMee010.ts @@ -0,0 +1,50 @@ +import { HttpStatusCode } from "axios"; +import { z } from "zod"; + +/** + * @version v0.1 + * @description 하나의 회의에 대해 안건을 삭제합니다. (해당 안건을 하나의 회의에서 제외) + */ + +const url = (meetingId: number, agendaId: number) => + `/executive/meetings/meeting/${meetingId}/agendas/agenda/${agendaId}`; +const method = "DELETE"; + +const requestParam = z.object({ + meetingId: z.coerce.number().int().min(1), + agendaId: z.coerce.number().int().min(1), +}); + +const requestQuery = z.object({}); + +const requestBody = z.object({}); + +const responseBodyMap = { + [HttpStatusCode.Ok]: z.object({}), +}; + +const responseErrorMap = {}; + +const apiMee010 = { + url, + method, + requestParam, + requestQuery, + requestBody, + responseBodyMap, + responseErrorMap, +}; + +type ApiMee010RequestParam = z.infer; +type ApiMee010RequestQuery = z.infer; +type ApiMee010RequestBody = z.infer; +type ApiMee010ResponseOk = z.infer<(typeof apiMee010.responseBodyMap)[200]>; + +export default apiMee010; + +export type { + ApiMee010RequestParam, + ApiMee010RequestQuery, + ApiMee010RequestBody, + ApiMee010ResponseOk, +}; From 1f84d70637a8486b92b437ebbed5855f198986ff Mon Sep 17 00:00:00 2001 From: chacha Date: Wed, 20 Nov 2024 22:10:09 +0900 Subject: [PATCH 4/8] fix: merge conflict --- packages/api/src/feature/meeting/meeting.repository.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index 8b281b6e9..40c1f64c1 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Inject, Injectable } from "@nestjs/common"; -import { and, eq, gte, isNull, lt } from "drizzle-orm"; +import { and, eq, gte, isNull, lt, max } from "drizzle-orm"; import { MySql2Database } from "drizzle-orm/mysql2"; import logger from "@sparcs-clubs/api/common/util/logger"; From 9ffcdf05fd1f657c75800f6bc846e8f27840cd15 Mon Sep 17 00:00:00 2001 From: chacha Date: Mon, 25 Nov 2024 01:57:09 +0900 Subject: [PATCH 5/8] fix: change meeting schema and create agenda module --- .../api/src/drizzle/schema/meeting.schema.ts | 4 + .../meeting/agenda/agenda.controller.ts | 88 +++++++++++++++++ .../feature/meeting/agenda/agenda.module.ts | 17 ++++ .../feature/meeting/agenda/agenda.service.ts | 95 +++++++++++++++++++ .../src/feature/meeting/meeting.controller.ts | 80 +--------------- .../src/feature/meeting/meeting.repository.ts | 60 ++++++++---- .../src/feature/meeting/meeting.service.ts | 77 --------------- 7 files changed, 246 insertions(+), 175 deletions(-) create mode 100644 packages/api/src/feature/meeting/agenda/agenda.controller.ts create mode 100644 packages/api/src/feature/meeting/agenda/agenda.module.ts create mode 100644 packages/api/src/feature/meeting/agenda/agenda.service.ts diff --git a/packages/api/src/drizzle/schema/meeting.schema.ts b/packages/api/src/drizzle/schema/meeting.schema.ts index d8dae6121..3188adafa 100644 --- a/packages/api/src/drizzle/schema/meeting.schema.ts +++ b/packages/api/src/drizzle/schema/meeting.schema.ts @@ -42,6 +42,7 @@ export const Meeting = mysqlTable( startDate: datetime("start_datetime").notNull(), endDate: datetime("end_datetime"), tag: varchar("meeting_group_tag", { length: 255 }).notNull(), + statusEnumId: int("meeting_status_enum").notNull().default(1), // CHACHA : NEW! createdAt: timestamp("created_at").defaultNow(), updatedAt: timestamp("updated_at").defaultNow().onUpdateNow(), deletedAt: timestamp("deleted_at"), @@ -203,6 +204,9 @@ export const MeetingMapping = mysqlTable( meetingAgendaContentId: int("meeting_agenda_content_id"), meetingAgendaVoteId: int("meeting_agenda_vote_id"), meetingAgendaEntityPosition: int("meeting_agenda_entity_position"), + createdAt: timestamp("created_at").defaultNow(), // CHACHA : NEW! + updatedAt: timestamp("updated_at").defaultNow().onUpdateNow(), // CHACHA : NEW! + deletedAt: timestamp("deleted_at"), // CHACHA : NEW! }, table => ({ meetingMeetingMappingIdFk: foreignKey({ diff --git a/packages/api/src/feature/meeting/agenda/agenda.controller.ts b/packages/api/src/feature/meeting/agenda/agenda.controller.ts new file mode 100644 index 000000000..7126ae5a4 --- /dev/null +++ b/packages/api/src/feature/meeting/agenda/agenda.controller.ts @@ -0,0 +1,88 @@ +import { + Body, + Controller, + Delete, + Param, + Patch, + Post, + UsePipes, +} from "@nestjs/common"; + +import apiMee006, { + ApiMee006RequestBody, + ApiMee006RequestParam, + ApiMee006ResponseCreated, +} from "@sparcs-clubs/interface/api/meeting/apiMee006"; + +import apiMee008, { + ApiMee008RequestBody, + ApiMee008RequestParam, + ApiMee008ResponseOk, +} from "@sparcs-clubs/interface/api/meeting/apiMee008"; + +import apiMee010, { + ApiMee010RequestParam, + ApiMee010ResponseOk, +} from "@sparcs-clubs/interface/api/meeting/apiMee010"; + +import { ZodPipe } from "@sparcs-clubs/api/common/pipe/zod-pipe"; +import { Executive } from "@sparcs-clubs/api/common/util/decorators/method-decorator"; +import { GetExecutive } from "@sparcs-clubs/api/common/util/decorators/param-decorator"; + +import { AgendaService } from "./agenda.service"; + +@Controller() +export default class AgendaController { + constructor(private meetingService: AgendaService) {} + + @Executive() + @Post("/executive/meetings/meeting/:meetingId/agendas/agenda") + @UsePipes(new ZodPipe(apiMee006)) + async postExecutiveMeetingAgenda( + @GetExecutive() user: GetExecutive, + @Param() { meetingId }: ApiMee006RequestParam, + @Body() { meetingEnumId, description, title }: ApiMee006RequestBody, + ): Promise { + const result = await this.meetingService.postExecutiveMeetingAgenda( + user.executiveId, + meetingId, + meetingEnumId, + description, + title, + ); + return result; + } + + @Executive() + @Patch("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") + @UsePipes(new ZodPipe(apiMee008)) + async patchExecutiveMeetingAgenda( + @GetExecutive() user: GetExecutive, + @Param() { agendaId }: ApiMee008RequestParam, + @Body() { agendaEnumId, description, title }: ApiMee008RequestBody, + ): Promise { + const result = await this.meetingService.patchExecutiveMeetingAgenda( + user.executiveId, + agendaId, + agendaEnumId, + description, + title, + ); + return result; + } + + @Executive() + @Delete("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") + @UsePipes(new ZodPipe(apiMee010)) + async deleteExecutiveMeetingAgenda( + @GetExecutive() user: GetExecutive, + @Param() { meetingId, agendaId }: ApiMee010RequestParam, + ): Promise { + const result = await this.meetingService.deleteExecutiveMeetingAgenda( + user.executiveId, + meetingId, + agendaId, + ); + return result; + } +} diff --git a/packages/api/src/feature/meeting/agenda/agenda.module.ts b/packages/api/src/feature/meeting/agenda/agenda.module.ts new file mode 100644 index 000000000..1716066b3 --- /dev/null +++ b/packages/api/src/feature/meeting/agenda/agenda.module.ts @@ -0,0 +1,17 @@ +import { Module } from "@nestjs/common"; + +import { DrizzleModule } from "@sparcs-clubs/api/drizzle/drizzle.module"; +import UserModule from "@sparcs-clubs/api/feature/user/user.module"; + +import { MeetingRepository } from "../meeting.repository"; + +import AgendaController from "./agenda.controller"; + +import { AgendaService } from "./agenda.service"; + +@Module({ + imports: [DrizzleModule, UserModule], + providers: [AgendaService, MeetingRepository], + controllers: [AgendaController], +}) +export class AgendaModule {} diff --git a/packages/api/src/feature/meeting/agenda/agenda.service.ts b/packages/api/src/feature/meeting/agenda/agenda.service.ts new file mode 100644 index 000000000..b577f8593 --- /dev/null +++ b/packages/api/src/feature/meeting/agenda/agenda.service.ts @@ -0,0 +1,95 @@ +import { + HttpException, + HttpStatus, + Injectable, + UnauthorizedException, +} from "@nestjs/common"; + +import { ApiMee006ResponseCreated } from "@sparcs-clubs/interface/api/meeting/apiMee006"; + +import { ApiMee008ResponseOk } from "@sparcs-clubs/interface/api/meeting/apiMee008"; + +import { ApiMee010ResponseOk } from "@sparcs-clubs/interface/api/meeting/apiMee010"; + +import UserPublicService from "@sparcs-clubs/api/feature/user/service/user.public.service"; + +import { MeetingRepository } from "../meeting.repository"; + +@Injectable() +export class AgendaService { + constructor( + private readonly meetingRepository: MeetingRepository, + private readonly userPublicService: UserPublicService, + ) {} + + async postExecutiveMeetingAgenda( + executiveId: number, + meetingId: number, + meetingEnumId: number, + description: string, + title: string, + ): Promise { + const user = await this.userPublicService.getExecutiveById({ + id: executiveId, + }); + + if (!user) { + throw new UnauthorizedException("Not allowed type"); + } + + await this.meetingRepository.insertMeetingAgendaAndMapping( + meetingId, + meetingEnumId, + description, + title, + ); + + return {}; + } + + async patchExecutiveMeetingAgenda( + executiveId: number, + agendaId: number, + agendaEnumId: number, + description: string, + title: string, + ): Promise { + const user = await this.userPublicService.getExecutiveById({ + id: executiveId, + }); + + if (!user) { + throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); + } + + await this.meetingRepository.updateMeetingAgenda( + agendaId, + agendaEnumId, + description, + title, + ); + + return {}; + } + + async deleteExecutiveMeetingAgenda( + executiveId: number, + meetingId: number, + agendaId: number, + ): Promise { + const user = await this.userPublicService.getExecutiveById({ + id: executiveId, + }); + + if (!user) { + throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); + } + + await this.meetingRepository.deleteMeetingAgendaMapping( + meetingId, + agendaId, + ); + + return {}; + } +} diff --git a/packages/api/src/feature/meeting/meeting.controller.ts b/packages/api/src/feature/meeting/meeting.controller.ts index 92ed22e20..fcb432c80 100644 --- a/packages/api/src/feature/meeting/meeting.controller.ts +++ b/packages/api/src/feature/meeting/meeting.controller.ts @@ -1,37 +1,10 @@ -import { - Body, - Controller, - Delete, - Get, - Param, - Patch, - Post, - Query, - UsePipes, -} from "@nestjs/common"; +import { Controller, Get, Query, UsePipes } from "@nestjs/common"; import apiMee005, { ApiMee005RequestQuery, ApiMee005ResponseOk, } from "@sparcs-clubs/interface/api/meeting/apiMee005"; -import apiMee006, { - ApiMee006RequestBody, - ApiMee006RequestParam, - ApiMee006ResponseCreated, -} from "@sparcs-clubs/interface/api/meeting/apiMee006"; - -import apiMee008, { - ApiMee008RequestBody, - ApiMee008RequestParam, - ApiMee008ResponseOk, -} from "@sparcs-clubs/interface/api/meeting/apiMee008"; - -import apiMee010, { - ApiMee010RequestParam, - ApiMee010ResponseOk, -} from "@sparcs-clubs/interface/api/meeting/apiMee010"; - import { ZodPipe } from "@sparcs-clubs/api/common/pipe/zod-pipe"; import { Executive } from "@sparcs-clubs/api/common/util/decorators/method-decorator"; import { GetExecutive } from "@sparcs-clubs/api/common/util/decorators/param-decorator"; @@ -56,55 +29,4 @@ export default class MeetingController { return { degree }; } - - @Executive() - @Post("/executive/meetings/meeting/:meetingId/agendas/agenda") - @UsePipes(new ZodPipe(apiMee006)) - async postExecutiveMeetingAgenda( - @GetExecutive() user: GetExecutive, - @Param() { meetingId }: ApiMee006RequestParam, - @Body() { meetingEnumId, description, title }: ApiMee006RequestBody, - ): Promise { - const result = await this.meetingService.postExecutiveMeetingAgenda( - user.executiveId, - meetingId, - meetingEnumId, - description, - title, - ); - return result; - } - - @Executive() - @Patch("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") - @UsePipes(new ZodPipe(apiMee008)) - async patchExecutiveMeetingAgenda( - @GetExecutive() user: GetExecutive, - @Param() { agendaId }: ApiMee008RequestParam, - @Body() { agendaEnumId, description, title }: ApiMee008RequestBody, - ): Promise { - const result = await this.meetingService.patchExecutiveMeetingAgenda( - user.executiveId, - agendaId, - agendaEnumId, - description, - title, - ); - return result; - } - - @Executive() - @Delete("/executive/meetings/meeting/:meetingId/agendas/agenda/:agendaId") - @UsePipes(new ZodPipe(apiMee010)) - async deleteExecutiveMeetingAgenda( - @GetExecutive() user: GetExecutive, - @Param() { meetingId, agendaId }: ApiMee010RequestParam, - ): Promise { - const result = await this.meetingService.deleteExecutiveMeetingAgenda( - user.executiveId, - meetingId, - agendaId, - ); - return result; - } } diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index 40c1f64c1..77a7b6ba2 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -348,7 +348,7 @@ export class MeetingRepository { .from(MeetingMapping) .where(and(eq(MeetingMapping.meetingId, meetingId))); - const maxAgendaPosition = getMax[0]?.value; + const maxAgendaPosition = getMax[0]?.value ?? 0; // CHACHA: undefined라면 0으로 set. const [insertMappingResult] = await this.db .insert(MeetingMapping) @@ -384,27 +384,49 @@ export class MeetingRepository { title: string, ) { const updateTime = new Date(); - const [result] = await this.db - .update(MeetingAgenda) - .set({ - MeetingAgendaEnum: agendaEnumId, - title, - description, - updatedAt: updateTime, - }) - .where(eq(MeetingAgenda.id, agendaId)); + const updateAgendaNotDeletedResult = await this.db.transaction(async tx => { + const checkDeleted = await tx + .select({ isDeleted: MeetingAgenda.deletedAt }) + .from(MeetingAgenda) + .where(eq(MeetingAgenda.id, agendaId)); + + if (checkDeleted.length === 0) { + logger.debug("[MeetingRepository] No such agenda exists."); // CHACHA: AgendatId가 유효한지 + return false; + } + + if (checkDeleted[0]?.isDeleted) { + logger.debug("[MeetingRepository] This agenda is deleted."); // CHACHA: Update 시에 deletedAt을 검사 + return false; + } + + const [result] = await tx + .update(MeetingAgenda) + .set({ + MeetingAgendaEnum: agendaEnumId, + title, + description, + updatedAt: updateTime, + }) + .where(eq(MeetingAgenda.id, agendaId)); + + if (result.affectedRows !== 1) { + logger.debug("[MeetingRepository] Failed to update meeting agenda."); + return false; + } + + return result; + }); - if (result.affectedRows !== 1) { - logger.debug("[MeetingRepository] Failed to update meeting agenda."); - return false; - } logger.debug(`[MeetingRepository] Updated meeting agenda: ${agendaId}`); - return result; + return updateAgendaNotDeletedResult; } async deleteMeetingAgendaMapping(meetingId: number, agendaId: number) { - const [meetingAgendaMappingDeleteResult] = await this.db - .delete(MeetingMapping) + const deleteTime = new Date(); + const [meetingAgendaMappingDeleteResult] = await this.db // CHACHA: soft delete로 수정! + .update(MeetingMapping) + .set({ deletedAt: deleteTime }) .where( and( eq(MeetingMapping.meetingId, meetingId), @@ -413,12 +435,12 @@ export class MeetingRepository { ); if (meetingAgendaMappingDeleteResult.affectedRows !== 1) { logger.debug( - "[MeetingRepository] Failed to delete meeting agenda mapping.", + "[MeetingRepository] Failed to soft delete meeting agenda mapping.", ); return false; } logger.debug( - `[MeetingRepository] Deleted meeting agenda mapping: ${meetingId}, ${agendaId}`, + `[MeetingRepository] Soft deleted meeting agenda mapping: ${meetingId}, ${agendaId}`, ); return meetingAgendaMappingDeleteResult; } diff --git a/packages/api/src/feature/meeting/meeting.service.ts b/packages/api/src/feature/meeting/meeting.service.ts index d2ff73e18..fed1cae27 100644 --- a/packages/api/src/feature/meeting/meeting.service.ts +++ b/packages/api/src/feature/meeting/meeting.service.ts @@ -2,12 +2,6 @@ import { HttpException, HttpStatus, Injectable } from "@nestjs/common"; import { WsException } from "@nestjs/websockets"; -import { ApiMee006ResponseCreated } from "@sparcs-clubs/interface/api/meeting/apiMee006"; - -import { ApiMee008ResponseOk } from "@sparcs-clubs/interface/api/meeting/apiMee008"; - -import { ApiMee010ResponseOk } from "@sparcs-clubs/interface/api/meeting/apiMee010"; - import UserPublicService from "../user/service/user.public.service"; import { MeetingRepository } from "./meeting.repository"; @@ -61,75 +55,4 @@ export class MeetingService { return result; } - - async postExecutiveMeetingAgenda( - executiveId: number, - meetingId: number, - meetingEnumId: number, - description: string, - title: string, - ): Promise { - const user = await this.userPublicService.getExecutiveById({ - id: executiveId, - }); - - if (!user) { - throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); - } - - await this.meetingRepository.insertMeetingAgendaAndMapping( - meetingId, - meetingEnumId, - description, - title, - ); - - return {}; - } - - async patchExecutiveMeetingAgenda( - executiveId: number, - agendaId: number, - agendaEnumId: number, - description: string, - title: string, - ): Promise { - const user = await this.userPublicService.getExecutiveById({ - id: executiveId, - }); - - if (!user) { - throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); - } - - await this.meetingRepository.updateMeetingAgenda( - agendaId, - agendaEnumId, - description, - title, - ); - - return {}; - } - - async deleteExecutiveMeetingAgenda( - executiveId: number, - meetingId: number, - agendaId: number, - ): Promise { - const user = await this.userPublicService.getExecutiveById({ - id: executiveId, - }); - - if (!user) { - throw new HttpException("Executive not found", HttpStatus.NOT_FOUND); - } - - await this.meetingRepository.deleteMeetingAgendaMapping( - meetingId, - agendaId, - ); - - return {}; - } } From e2372e1d9805d47bcfe4050aacdaf4fa10ac93e6 Mon Sep 17 00:00:00 2001 From: chacha Date: Mon, 25 Nov 2024 02:23:49 +0900 Subject: [PATCH 6/8] feat: add update on meeting table, status enum --- .../src/feature/meeting/meeting.repository.ts | 90 +++++++++++++------ 1 file changed, 65 insertions(+), 25 deletions(-) diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index 77a7b6ba2..ef657978c 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -350,14 +350,12 @@ export class MeetingRepository { const maxAgendaPosition = getMax[0]?.value ?? 0; // CHACHA: undefined라면 0으로 set. - const [insertMappingResult] = await this.db - .insert(MeetingMapping) - .values({ - meetingId, - meetingAgendaId: agendaId, - meetingAgendaPosition: maxAgendaPosition + 1, - meetingAgendaEntityType: 3, // no agenda entity mapped yet. - }); + const [insertMappingResult] = await tx.insert(MeetingMapping).values({ + meetingId, + meetingAgendaId: agendaId, + meetingAgendaPosition: maxAgendaPosition + 1, + meetingAgendaEntityType: 3, // no agenda entity mapped yet. + }); if (insertMappingResult.affectedRows !== 1) { logger.debug("[MeetingRepository] Failed to insert meeting agenda"); @@ -370,6 +368,14 @@ export class MeetingRepository { `[MeetingRepository] Inserted meeting agenda mapping: ${meetingMappingId}`, ); + await tx + .update(Meeting) + .set({ statusEnumId: 2 }) + .where(eq(MeetingMapping.meetingId, meetingId)); + logger.debug( + `[MeetingRepository] Updated meeting status, meetingId: ${meetingId}`, // CHACHA: meeting-agenda mapping이 생겼으므로 안건 공개 상태로 변경! + ); + return true; }, ); @@ -424,24 +430,58 @@ export class MeetingRepository { async deleteMeetingAgendaMapping(meetingId: number, agendaId: number) { const deleteTime = new Date(); - const [meetingAgendaMappingDeleteResult] = await this.db // CHACHA: soft delete로 수정! - .update(MeetingMapping) - .set({ deletedAt: deleteTime }) - .where( - and( - eq(MeetingMapping.meetingId, meetingId), - eq(MeetingMapping.meetingAgendaId, agendaId), - ), - ); - if (meetingAgendaMappingDeleteResult.affectedRows !== 1) { - logger.debug( - "[MeetingRepository] Failed to soft delete meeting agenda mapping.", - ); - return false; - } - logger.debug( - `[MeetingRepository] Soft deleted meeting agenda mapping: ${meetingId}, ${agendaId}`, + const meetingAgendaMappingDeleteResult = await this.db.transaction( + async tx => { + const [deleteResult] = await tx // CHACHA: soft delete로 수정! + .update(MeetingMapping) + .set({ deletedAt: deleteTime }) + .where( + and( + eq(MeetingMapping.meetingId, meetingId), + eq(MeetingMapping.meetingAgendaId, agendaId), + ), + ); + + if (deleteResult.affectedRows !== 1) { + logger.debug( + "[MeetingRepository] Failed to soft delete meeting agenda mapping.", + ); + return false; + } + + logger.debug( + `[MeetingRepository] Soft deleted meeting agenda mapping: ${meetingId}, ${agendaId}`, + ); + + const getEveryMappingDeletedAt = await tx + .select({ isDeleted: MeetingAgenda.deletedAt }) // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! + .from(MeetingMapping) + .where( + and( + eq(MeetingMapping.meetingId, meetingId), + eq(MeetingMapping.meetingAgendaId, agendaId), + ), + ); + + const deletedMapping = getEveryMappingDeletedAt.filter( + e => e.isDeleted, + ); + + if (deletedMapping.length < getEveryMappingDeletedAt.length) { + // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! + await tx + .update(Meeting) + .set({ statusEnumId: 1 }) + .where(eq(MeetingMapping.meetingId, meetingId)); + logger.debug( + `[MeetingRepository] Updated meeting status, meetingId: ${meetingId}`, + ); + } + + return deleteResult; + }, ); + return meetingAgendaMappingDeleteResult; } } From bdd33138f74880ad5e2479098f2acfc5724f5d55 Mon Sep 17 00:00:00 2001 From: chacha Date: Mon, 25 Nov 2024 02:58:09 +0900 Subject: [PATCH 7/8] feat: add module and fix repository --- .../api/src/feature/meeting/meeting.module.ts | 3 ++- .../src/feature/meeting/meeting.repository.ts | 24 +++++++------------ 2 files changed, 11 insertions(+), 16 deletions(-) diff --git a/packages/api/src/feature/meeting/meeting.module.ts b/packages/api/src/feature/meeting/meeting.module.ts index 189c5c0bb..917798108 100644 --- a/packages/api/src/feature/meeting/meeting.module.ts +++ b/packages/api/src/feature/meeting/meeting.module.ts @@ -4,6 +4,7 @@ import { DrizzleModule } from "src/drizzle/drizzle.module"; import UserModule from "../user/user.module"; +import { AgendaModule } from "./agenda/agenda.module"; import { AnnouncementModule } from "./announcement/announcement.module"; import MeetingController from "./meeting.controller"; import { MeetingGateway } from "./meeting.gateway"; @@ -11,7 +12,7 @@ import { MeetingRepository } from "./meeting.repository"; import { MeetingService } from "./meeting.service"; @Module({ - imports: [DrizzleModule, UserModule, AnnouncementModule], + imports: [DrizzleModule, UserModule, AnnouncementModule, AgendaModule], controllers: [MeetingController], providers: [MeetingGateway, MeetingService, MeetingRepository], }) diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index ef657978c..af6f2f443 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Inject, Injectable } from "@nestjs/common"; -import { and, eq, gte, isNull, lt, max } from "drizzle-orm"; +import { and, eq, gte, isNull, lt, max, sql } from "drizzle-orm"; import { MySql2Database } from "drizzle-orm/mysql2"; import logger from "@sparcs-clubs/api/common/util/logger"; @@ -371,7 +371,7 @@ export class MeetingRepository { await tx .update(Meeting) .set({ statusEnumId: 2 }) - .where(eq(MeetingMapping.meetingId, meetingId)); + .where(eq(Meeting.id, meetingId)); logger.debug( `[MeetingRepository] Updated meeting status, meetingId: ${meetingId}`, // CHACHA: meeting-agenda mapping이 생겼으므로 안건 공개 상태로 변경! ); @@ -389,7 +389,6 @@ export class MeetingRepository { description: string, title: string, ) { - const updateTime = new Date(); const updateAgendaNotDeletedResult = await this.db.transaction(async tx => { const checkDeleted = await tx .select({ isDeleted: MeetingAgenda.deletedAt }) @@ -412,7 +411,7 @@ export class MeetingRepository { MeetingAgendaEnum: agendaEnumId, title, description, - updatedAt: updateTime, + updatedAt: sql`NOW()`, }) .where(eq(MeetingAgenda.id, agendaId)); @@ -429,12 +428,11 @@ export class MeetingRepository { } async deleteMeetingAgendaMapping(meetingId: number, agendaId: number) { - const deleteTime = new Date(); const meetingAgendaMappingDeleteResult = await this.db.transaction( async tx => { const [deleteResult] = await tx // CHACHA: soft delete로 수정! .update(MeetingMapping) - .set({ deletedAt: deleteTime }) + .set({ deletedAt: sql`NOW()` }) .where( and( eq(MeetingMapping.meetingId, meetingId), @@ -454,25 +452,21 @@ export class MeetingRepository { ); const getEveryMappingDeletedAt = await tx - .select({ isDeleted: MeetingAgenda.deletedAt }) // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! + .select({ isDeleted: MeetingMapping.deletedAt }) // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! .from(MeetingMapping) - .where( - and( - eq(MeetingMapping.meetingId, meetingId), - eq(MeetingMapping.meetingAgendaId, agendaId), - ), - ); + .where(and(eq(MeetingMapping.meetingId, meetingId))); const deletedMapping = getEveryMappingDeletedAt.filter( + // CHACHA: deleted 된 mapping의 개수를 구하기 위함. e => e.isDeleted, ); - if (deletedMapping.length < getEveryMappingDeletedAt.length) { + if (deletedMapping.length === getEveryMappingDeletedAt.length) { // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! await tx .update(Meeting) .set({ statusEnumId: 1 }) - .where(eq(MeetingMapping.meetingId, meetingId)); + .where(eq(Meeting.id, meetingId)); logger.debug( `[MeetingRepository] Updated meeting status, meetingId: ${meetingId}`, ); From 3001dedeb9f0f704da541526c6d0f5dc63207ab3 Mon Sep 17 00:00:00 2001 From: chacha Date: Tue, 26 Nov 2024 02:07:36 +0900 Subject: [PATCH 8/8] fix: use count instead --- .../src/feature/meeting/meeting.repository.ts | 24 ++++++++++++------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/packages/api/src/feature/meeting/meeting.repository.ts b/packages/api/src/feature/meeting/meeting.repository.ts index af6f2f443..580e4a62f 100644 --- a/packages/api/src/feature/meeting/meeting.repository.ts +++ b/packages/api/src/feature/meeting/meeting.repository.ts @@ -1,6 +1,6 @@ import { HttpException, HttpStatus, Inject, Injectable } from "@nestjs/common"; -import { and, eq, gte, isNull, lt, max, sql } from "drizzle-orm"; +import { and, count, eq, gte, isNull, lt, max, not, sql } from "drizzle-orm"; import { MySql2Database } from "drizzle-orm/mysql2"; import logger from "@sparcs-clubs/api/common/util/logger"; @@ -451,17 +451,25 @@ export class MeetingRepository { `[MeetingRepository] Soft deleted meeting agenda mapping: ${meetingId}, ${agendaId}`, ); - const getEveryMappingDeletedAt = await tx - .select({ isDeleted: MeetingMapping.deletedAt }) // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! + const getEveryMappingByMeetingId = await tx + .select({ count: count() }) // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! .from(MeetingMapping) .where(and(eq(MeetingMapping.meetingId, meetingId))); - const deletedMapping = getEveryMappingDeletedAt.filter( - // CHACHA: deleted 된 mapping의 개수를 구하기 위함. - e => e.isDeleted, - ); + const getEveryDeletedMappingByMeetingId = await tx + .select({ count: count() }) + .from(MeetingMapping) + .where( + and( + eq(MeetingMapping.meetingId, meetingId), + not(eq(MeetingMapping.deletedAt, null)), + ), + ); - if (deletedMapping.length === getEveryMappingDeletedAt.length) { + if ( + getEveryMappingByMeetingId[0]?.count === + getEveryDeletedMappingByMeetingId[0]?.count + ) { // CHACHA: 만약 모든 Meeting과 Agenda mapping이 deleted -> 그 Meeting은 공고 게시 상태로! await tx .update(Meeting)