Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Impl MEE 006 008 010 api #1217

Open
wants to merge 8 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion packages/api/src/feature/meeting/meeting.controller.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,37 @@
import { Controller, Get, Query, UsePipes } from "@nestjs/common";
import {
Body,
Controller,
Delete,
Get,
Param,
Patch,
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 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";
Expand All @@ -29,4 +56,55 @@ 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<ApiMee006ResponseCreated> {
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<ApiMee008ResponseOk> {
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<ApiMee010ResponseOk> {
const result = await this.meetingService.deleteExecutiveMeetingAgenda(
user.executiveId,
meetingId,
agendaId,
);
return result;
}
}
112 changes: 111 additions & 1 deletion packages/api/src/feature/meeting/meeting.repository.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
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";
import { getKSTDate } from "@sparcs-clubs/api/common/util/util";
import {
Meeting,
MeetingAgenda,
MeetingAnnouncement,
MeetingAttendanceTimeT,
MeetingMapping,
MeetingVoteResult,
} from "@sparcs-clubs/api/drizzle/schema/meeting.schema";

Expand Down Expand Up @@ -312,4 +314,112 @@ export class MeetingRepository {

return result.length;
}

async insertMeetingAgendaAndMapping(
meetingId: number,
meetingEnumId: number,
description: string,
title: string,
) {
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,
});

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}`,
);

const getMax = await tx
ChaeyeonAhn marked this conversation as resolved.
Show resolved Hide resolved
.select({ value: max(MeetingMapping.meetingAgendaPosition) })
.from(MeetingMapping)
.where(and(eq(MeetingMapping.meetingId, meetingId)));

const maxAgendaPosition = getMax[0]?.value;

const [insertMappingResult] = await this.db
.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");
tx.rollback();
return false;
}

const meetingMappingId = insertMappingResult.insertId;
logger.debug(
`[MeetingRepository] Inserted meeting agenda mapping: ${meetingMappingId}`,
);

return true;
},
);

return isInsertAgendaAndMappingSuccess;
}

async updateMeetingAgenda(
agendaId: number,
agendaEnumId: number,
description: string,
title: string,
) {
const updateTime = new Date();
const [result] = await this.db
.update(MeetingAgenda)
ChaeyeonAhn marked this conversation as resolved.
Show resolved Hide resolved
.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;
}
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;
}
}
77 changes: 77 additions & 0 deletions packages/api/src/feature/meeting/meeting.service.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,12 @@ 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";
Expand Down Expand Up @@ -55,4 +61,75 @@ export class MeetingService {

return result;
}

async postExecutiveMeetingAgenda(
executiveId: number,
meetingId: number,
meetingEnumId: number,
description: string,
title: string,
): Promise<ApiMee006ResponseCreated> {
const user = await this.userPublicService.getExecutiveById({
id: executiveId,
});

if (!user) {
throw new HttpException("Executive not found", HttpStatus.NOT_FOUND);
ChaeyeonAhn marked this conversation as resolved.
Show resolved Hide resolved
}

await this.meetingRepository.insertMeetingAgendaAndMapping(
meetingId,
meetingEnumId,
description,
title,
);

return {};
}

async patchExecutiveMeetingAgenda(
executiveId: number,
agendaId: number,
agendaEnumId: number,
description: string,
title: string,
): Promise<ApiMee008ResponseOk> {
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<ApiMee010ResponseOk> {
const user = await this.userPublicService.getExecutiveById({
id: executiveId,
});

if (!user) {
throw new HttpException("Executive not found", HttpStatus.NOT_FOUND);
}

await this.meetingRepository.deleteMeetingAgendaMapping(
ChaeyeonAhn marked this conversation as resolved.
Show resolved Hide resolved
meetingId,
agendaId,
);

return {};
}
}
57 changes: 57 additions & 0 deletions packages/interface/src/api/meeting/apiMee006.ts
Original file line number Diff line number Diff line change
@@ -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<typeof apiMee006.requestParam>;
type ApiMee006RequestQuery = z.infer<typeof apiMee006.requestQuery>;
type ApiMee006RequestBody = z.infer<typeof apiMee006.requestBody>;
type ApiMee006ResponseCreated = z.infer<
(typeof apiMee006.responseBodyMap)[201]
>;

export default apiMee006;

export type {
ApiMee006RequestParam,
ApiMee006RequestQuery,
ApiMee006RequestBody,
ApiMee006ResponseCreated,
};
Loading