diff --git a/packages/api/src/app.module.ts b/packages/api/src/app.module.ts index dd0f27b..32a9167 100644 --- a/packages/api/src/app.module.ts +++ b/packages/api/src/app.module.ts @@ -2,9 +2,10 @@ import { Module } from "@nestjs/common"; import { AppController } from "./app.controller"; import { AppService } from "./app.service"; import { DrizzleModule } from "./drizzle/drizzle.module"; +import { OrganizationModule } from "./feature/organization/organization.module"; @Module({ - imports: [DrizzleModule], + imports: [DrizzleModule, OrganizationModule], controllers: [AppController], providers: [AppService], }) diff --git a/packages/api/src/drizzle/schema/enum.schema.ts b/packages/api/src/drizzle/schema/enum.schema.ts index 565697d..203f39e 100644 --- a/packages/api/src/drizzle/schema/enum.schema.ts +++ b/packages/api/src/drizzle/schema/enum.schema.ts @@ -1,7 +1,7 @@ import { InferSelectModel } from "drizzle-orm"; import { int, varchar, timestamp, mysqlTable } from "drizzle-orm/mysql-core"; -export const OrganizationTypeEnum = mysqlTable("org_typ_e", { +export const OrganizationTypeEnum = mysqlTable("organization_type_enum", { id: int("id").autoincrement().primaryKey().notNull(), name: varchar("name", { length: 30 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), @@ -9,15 +9,18 @@ export const OrganizationTypeEnum = mysqlTable("org_typ_e", { deletedAt: timestamp("deleted_at"), }); -export const OrganizationPresidentTypeEnum = mysqlTable("org_pre_typ_e", { - id: int("id").autoincrement().primaryKey().notNull(), - name: varchar("name", { length: 30 }).notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), - deletedAt: timestamp("deleted_at"), -}); +export const OrganizationPresidentTypeEnum = mysqlTable( + "organization_president_type_enum", + { + id: int("id").autoincrement().primaryKey().notNull(), + name: varchar("name", { length: 30 }).notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), + deletedAt: timestamp("deleted_at"), + }, +); -export const BudgetDomainEnum = mysqlTable("bud_dom_e", { +export const BudgetDomainEnum = mysqlTable("budget_domain_enum", { id: int("id").autoincrement().primaryKey().notNull(), name: varchar("name", { length: 30 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), @@ -25,23 +28,29 @@ export const BudgetDomainEnum = mysqlTable("bud_dom_e", { deletedAt: timestamp("deleted_at"), }); -export const BudgetDivisionIncomeEnum = mysqlTable("bud_div_inc_e", { - id: int("id").autoincrement().primaryKey().notNull(), - name: varchar("name", { length: 30 }).notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), - deletedAt: timestamp("deleted_at"), -}); +export const BudgetDivisionIncomeEnum = mysqlTable( + "budget_division_income_enum", + { + id: int("id").autoincrement().primaryKey().notNull(), + name: varchar("name", { length: 30 }).notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), + deletedAt: timestamp("deleted_at"), + }, +); -export const BudgetDivisionExpenseEnum = mysqlTable("bud_div_exp_e", { - id: int("id").autoincrement().primaryKey().notNull(), - name: varchar("name", { length: 30 }).notNull(), - createdAt: timestamp("created_at").defaultNow().notNull(), - updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), - deletedAt: timestamp("deleted_at"), -}); +export const BudgetDivisionExpenseEnum = mysqlTable( + "budget_division_expense_enum", + { + id: int("id").autoincrement().primaryKey().notNull(), + name: varchar("name", { length: 30 }).notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), + deletedAt: timestamp("deleted_at"), + }, +); -export const BudgetClassExpenseEnum = mysqlTable("bud_cla_exp_e", { +export const BudgetClassExpenseEnum = mysqlTable("budget_class_expense_enum", { id: int("id").autoincrement().primaryKey().notNull(), name: varchar("name", { length: 30 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), @@ -49,7 +58,7 @@ export const BudgetClassExpenseEnum = mysqlTable("bud_cla_exp_e", { deletedAt: timestamp("deleted_at"), }); -export const TransactionTypeEnum = mysqlTable("tra_typ_e", { +export const TransactionTypeEnum = mysqlTable("transaction_type_enum", { id: int("id").autoincrement().primaryKey().notNull(), name: varchar("name", { length: 30 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), @@ -57,7 +66,7 @@ export const TransactionTypeEnum = mysqlTable("tra_typ_e", { deletedAt: timestamp("deleted_at"), }); -export const ReportFileTypeEnum = mysqlTable("rep_fil_typ_e", { +export const ReportFileTypeEnum = mysqlTable("report_file_type_enum", { id: int("id").autoincrement().primaryKey().notNull(), name: varchar("name", { length: 30 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), @@ -65,7 +74,7 @@ export const ReportFileTypeEnum = mysqlTable("rep_fil_typ_e", { deletedAt: timestamp("deleted_at"), }); -export const AssistantPermissionEnum = mysqlTable("ass_per_e", { +export const AssistantPermissionEnum = mysqlTable("assistant_permission_enum", { id: int("id").autoincrement().primaryKey().notNull(), name: varchar("name", { length: 30 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), @@ -73,9 +82,23 @@ export const AssistantPermissionEnum = mysqlTable("ass_per_e", { deletedAt: timestamp("deleted_at"), }); +export const AgendaAcceptedStatusEnum = mysqlTable( + "agenda_accepted_status_enum", + { + id: int("id").autoincrement().primaryKey().notNull(), + name: varchar("name", { length: 30 }).notNull(), + createdAt: timestamp("created_at").defaultNow().notNull(), + updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), + deletedAt: timestamp("deleted_at"), + }, +); + export type OrganizationTypeEnumT = InferSelectModel< typeof OrganizationTypeEnum >; export type OrganizationPresidentTypeEnumT = InferSelectModel< typeof OrganizationPresidentTypeEnum >; +export type AgendaAcceptedStatusEnumT = InferSelectModel< + typeof AgendaAcceptedStatusEnum +>; diff --git a/packages/api/src/drizzle/schema/organization.schema.ts b/packages/api/src/drizzle/schema/organization.schema.ts index 3e07eac..a668ae9 100644 --- a/packages/api/src/drizzle/schema/organization.schema.ts +++ b/packages/api/src/drizzle/schema/organization.schema.ts @@ -51,6 +51,7 @@ export const OrganizationPresident = mysqlTable( organizationPresidentTypeEnumId: int( "organization_president_type_enum_id", ).notNull(), + organizationId: int("organization_id").notNull(), phoneNumber: varchar("phone_number", { length: 20 }).notNull(), createdAt: timestamp("created_at").defaultNow().notNull(), updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(), @@ -61,6 +62,10 @@ export const OrganizationPresident = mysqlTable( columns: [table.userId], foreignColumns: [User.id], }), + organizationIdFk: foreignKey({ + columns: [table.organizationId], + foreignColumns: [Organization.id], + }), }), ); diff --git a/packages/api/src/feature/organization/controller/organization.controller.ts b/packages/api/src/feature/organization/controller/organization.controller.ts index 3882d8d..d28af44 100644 --- a/packages/api/src/feature/organization/controller/organization.controller.ts +++ b/packages/api/src/feature/organization/controller/organization.controller.ts @@ -10,7 +10,7 @@ import { import { OrganizationService } from "../service/organization.service"; -@Controller("organization") +@Controller() export class OrganizationController { constructor(private readonly organizationService: OrganizationService) {} diff --git a/packages/api/src/feature/organization/organization.module.ts b/packages/api/src/feature/organization/organization.module.ts index 03a95c7..29b0c8a 100644 --- a/packages/api/src/feature/organization/organization.module.ts +++ b/packages/api/src/feature/organization/organization.module.ts @@ -4,10 +4,11 @@ import { DrizzleModule } from "src/drizzle/drizzle.module"; import { SemesterModule } from "src/feature/semester/semester.module"; import { OrganizationService } from "./service/organization.service"; import { OrganizationController } from "./controller/organization.controller"; +import { OrganizationRepository } from "./repository/organization.repository"; @Module({ imports: [DrizzleModule, SemesterModule], controllers: [OrganizationController], - providers: [OrganizationService], + providers: [OrganizationService, OrganizationRepository], }) export class OrganizationModule {} diff --git a/packages/api/src/feature/organization/repository/organization.repository.ts b/packages/api/src/feature/organization/repository/organization.repository.ts index 3b0d8c0..ee541a3 100644 --- a/packages/api/src/feature/organization/repository/organization.repository.ts +++ b/packages/api/src/feature/organization/repository/organization.repository.ts @@ -1,5 +1,5 @@ import { Injectable, Inject } from "@nestjs/common"; -import { and, or, lte, gte, eq } from "drizzle-orm"; +import { and, or, lte, gte, eq, isNull } from "drizzle-orm"; import { MySql2Database } from "drizzle-orm/mysql2"; import { DrizzleAsyncProvider } from "src/drizzle/drizzle.provider"; @@ -8,14 +8,69 @@ import { OrganizationTypeEnum, OrganizationT, OrganizationTypeEnumT, + User, + UserStudent, + OrganizationPresident, + OrganizationPresidentT, + UserT, + UserStudentT, } from "src/drizzle/schema"; +export type OrganizationWithPresidentT = { + organization: OrganizationT; + organizationType: OrganizationTypeEnumT; + president: OrganizationPresidentT; + user: UserT; + userStudent: UserStudentT; +}; @Injectable() export class OrganizationRepository { constructor( @Inject(DrizzleAsyncProvider) private readonly db: MySql2Database, ) {} + async getOrganizationById(id: number): Promise { + return this.db.select().from(Organization).where(eq(Organization.id, id)); + } + + async getOrganizationWithPresidentById( + id: number, + date: Date, + ): Promise { + const res = await this.db + .select() + .from(Organization) + .innerJoin( + OrganizationTypeEnum, + eq(Organization.organizationTypeEnumId, OrganizationTypeEnum.id), + ) + .innerJoin( + OrganizationPresident, + eq(Organization.id, OrganizationPresident.organizationId), + ) + .innerJoin(User, eq(OrganizationPresident.userId, User.id)) + .innerJoin(UserStudent, eq(UserStudent.userId, User.id)) + .where( + and( + eq(Organization.id, id), + and( + lte(OrganizationPresident.startTerm, date), + or( + gte(OrganizationPresident.endTerm, date), + isNull(OrganizationPresident.endTerm), + ), + ), + ), + ); + return res.map(row => ({ + organization: row.organization, + organizationType: row.organization_type_enum, + president: row.organization_president, + user: row.user, + userStudent: row.user_student, + })); + } + async getOrganizationsByTerms( startTerm: Date, endTerm: Date, @@ -40,11 +95,14 @@ export class OrganizationRepository { ), and( lte(Organization.startTerm, endTerm), - eq(Organization.endTerm, null), + isNull(Organization.endTerm), ), ), ); - return res.map(row => ({ ...row, organizationTypeEnum: row.org_typ_e })); + return res.map(row => ({ + ...row, + organizationTypeEnum: row.organization_type_enum, + })); } } diff --git a/packages/api/src/feature/organization/service/organization.public.service.ts b/packages/api/src/feature/organization/service/organization.public.service.ts new file mode 100644 index 0000000..1b09cb3 --- /dev/null +++ b/packages/api/src/feature/organization/service/organization.public.service.ts @@ -0,0 +1,64 @@ +import { Injectable, NotFoundException } from "@nestjs/common"; +import { OrganizationT } from "src/drizzle/schema"; +import { SemesterPublicService } from "src/feature/semester/semester.public.service"; +import { + OrganizationRepository, + OrganizationWithPresidentT, +} from "../repository/organization.repository"; + +@Injectable() +export class OrganizationPublicService { + constructor( + private readonly organizationRepository: OrganizationRepository, + private readonly semesterPublicService: SemesterPublicService, + ) {} + + /** + * @param id organization id + * @returns OrganizationT id에 해당하는 OrganizationT 객체를 리턴합니다. + * @description 해당 id의 organization이 없으면 404 exception을 throw 합니다. + */ + async getOrganizationById(id: number): Promise { + const organizations = + await this.organizationRepository.getOrganizationById(id); + if (organizations.length === 0) { + throw new NotFoundException(`Organization with ID ${id} not found.`); + } + return organizations[0]; + } + + /** + * @param id organizationId, date + * @returns OrganizationT id에 해당하는 OrganizationT 객체를 리턴합니다. + * @description 해당 id의 organization이 없으면 404 exception을 throw 합니다. + */ + async getOrganizationWithPresidentByIdAndDate( + id: number, + date: Date, + ): Promise { + const organizations = + await this.organizationRepository.getOrganizationWithPresidentById( + id, + date, + ); + if (organizations.length === 0) { + throw new NotFoundException(`Organization with ID ${id} not found.`); + } + return organizations[0]; + } + + /** + * @param id organizationId, semesterId + * @returns OrganizationT id에 해당하는 semester의 마지막 OrganizationWithPresidentT 객체를 리턴합니다. + * @description 해당 id의 organization이 없으면 404 exception을 throw 합니다. + */ + async getOrganizationWithPresidentByIdAndSemester( + id: number, + semesterId: number, + ): Promise { + const { endTerm } = + await this.semesterPublicService.getSemesterById(semesterId); + + return this.getOrganizationWithPresidentByIdAndDate(id, endTerm); + } +} diff --git a/packages/api/src/feature/organization/service/organization.service.ts b/packages/api/src/feature/organization/service/organization.service.ts index 2651040..dcc6b05 100644 --- a/packages/api/src/feature/organization/service/organization.service.ts +++ b/packages/api/src/feature/organization/service/organization.service.ts @@ -5,7 +5,7 @@ import { ApiOrg001ResponseOK, } from "@sparcs-students/interface/api/organization/index"; -import SemesterPublicService from "src/feature/semester/semester.public.service"; +import { SemesterPublicService } from "src/feature/semester/semester.public.service"; import { OrganizationRepository } from "../repository/organization.repository"; diff --git a/packages/api/src/feature/semester/semester.module.ts b/packages/api/src/feature/semester/semester.module.ts index 74ab120..0854c3e 100644 --- a/packages/api/src/feature/semester/semester.module.ts +++ b/packages/api/src/feature/semester/semester.module.ts @@ -1,6 +1,11 @@ import { Module } from "@nestjs/common"; import { DrizzleModule } from "@sparcs-students/api/drizzle/drizzle.module"; import { SemesterRepository } from "./semester.repository"; +import { SemesterPublicService } from "./semester.public.service"; -@Module({ exports: [SemesterRepository], imports: [DrizzleModule] }) +@Module({ + providers: [SemesterRepository, SemesterPublicService], + exports: [SemesterPublicService], + imports: [DrizzleModule], +}) export class SemesterModule {} diff --git a/packages/api/src/feature/semester/semester.public.service.ts b/packages/api/src/feature/semester/semester.public.service.ts index 53568da..eff1eff 100644 --- a/packages/api/src/feature/semester/semester.public.service.ts +++ b/packages/api/src/feature/semester/semester.public.service.ts @@ -3,7 +3,7 @@ import { SemesterT } from "@sparcs-students/api/drizzle/schema"; import { SemesterRepository } from "./semester.repository"; @Injectable() -export default class SemesterPublicService { +export class SemesterPublicService { constructor(private semesterRepository: SemesterRepository) {} /** @@ -16,6 +16,7 @@ export default class SemesterPublicService { if (semesters.length === 0) { throw new NotFoundException(`Semester with ID ${id} not found.`); } + return semesters[0]; } }