Skip to content

Commit

Permalink
Merge branch 'dev' into 56-impl-post-apis-for-filling-dbs
Browse files Browse the repository at this point in the history
  • Loading branch information
Gerbera3090 authored Nov 25, 2024
2 parents edf6e1e + eb515c4 commit 08e87ac
Show file tree
Hide file tree
Showing 17 changed files with 611 additions and 10 deletions.
3 changes: 2 additions & 1 deletion packages/api/src/app.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,10 @@ import { AppController } from "./app.controller";
import { AppService } from "./app.service";
import { DrizzleModule } from "./drizzle/drizzle.module";
import { OrganizationModule } from "./feature/organization/organization.module";
import { ProposalModule } from "./feature/proposal/proposal.module";

@Module({
imports: [DrizzleModule, OrganizationModule],
imports: [DrizzleModule, OrganizationModule, ProposalModule],
controllers: [AppController],
providers: [AppService],
})
Expand Down
4 changes: 4 additions & 0 deletions packages/api/src/drizzle/schema/meeting.schema.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { InferSelectModel } from "drizzle-orm";
import {
int,
varchar,
Expand Down Expand Up @@ -39,3 +40,6 @@ export const Agenda = mysqlTable(
}),
}),
);

export type MeetingT = InferSelectModel<typeof Meeting>;
export type AgendaT = InferSelectModel<typeof Agenda>;
31 changes: 31 additions & 0 deletions packages/api/src/drizzle/schema/proposal.schema.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
mysqlTable,
foreignKey,
} from "drizzle-orm/mysql-core";
import { InferSelectModel } from "drizzle-orm";
import { Organization, Team } from "./organization.schema";
import { Semester } from "./semester.schema";
import { User } from "./user.schema";
Expand Down Expand Up @@ -61,6 +62,7 @@ export const ProjectProposalRevision = mysqlTable(
target: text("target").notNull(),
detail: text("detail").notNull(),
note: text("note"),
agendaId: int("agenda_id"),
createdAt: timestamp("created_at").defaultNow().notNull(),
updatedAt: timestamp("updated_at").defaultNow().onUpdateNow().notNull(),
deletedAt: timestamp("deleted_at"),
Expand All @@ -79,6 +81,11 @@ export const ProjectProposalRevision = mysqlTable(
foreignColumns: [ProjectProposal.id],
name: "pro_pro_rev_document_id_fk",
}),
agendaIdFk: foreignKey({
columns: [table.agendaId],
foreignColumns: [Agenda.id],
name: "pro_pro_rev_agenda_id_fk",
}),
}),
);

Expand Down Expand Up @@ -299,3 +306,27 @@ export const BudgetProposalExpenseRevision = mysqlTable(
}),
}),
);

export type ProjectProposalT = InferSelectModel<typeof ProjectProposal>;
export type ProjectProposalRevisionT = InferSelectModel<
typeof ProjectProposalRevision
>;
export type ProjectProposalTimelineT = InferSelectModel<
typeof ProjectProposalTimeline
>;
export type OperationProposalT = InferSelectModel<typeof OperationProposal>;
export type OperatingCommitteeProposalT = InferSelectModel<
typeof OperatingCommitteeProposal
>;
export type BudgetProposalIncomeT = InferSelectModel<
typeof BudgetProposalIncome
>;
export type BudgetProposalIncomeRevisionT = InferSelectModel<
typeof BudgetProposalIncomeRevision
>;
export type BudgetProposalExpenseT = InferSelectModel<
typeof BudgetProposalExpense
>;
export type BudgetProposalExpenseRevisionT = InferSelectModel<
typeof BudgetProposalExpenseRevision
>;
8 changes: 7 additions & 1 deletion packages/api/src/feature/organization/organization.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,17 @@ 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 { OrganizationPublicService } from "./service/organization.public.service";
import { OrganizationRepository } from "./repository/organization.repository";

@Module({
imports: [DrizzleModule, SemesterModule],
controllers: [OrganizationController],
providers: [OrganizationService, OrganizationRepository],
providers: [
OrganizationService,
OrganizationRepository,
OrganizationPublicService,
],
exports: [OrganizationPublicService],
})
export class OrganizationModule {}
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ export type OrganizationWithPresidentT = {
user: UserT;
userStudent: UserStudentT;
};

@Injectable()
export class OrganizationRepository {
constructor(
Expand All @@ -35,7 +36,7 @@ export class OrganizationRepository {
}

async getOrganizationWithPresidentById(
id: number,
organizationId: number,
date: Date,
): Promise<OrganizationWithPresidentT[]> {
const res = await this.db
Expand All @@ -53,7 +54,8 @@ export class OrganizationRepository {
.innerJoin(UserStudent, eq(UserStudent.userId, User.id))
.where(
and(
eq(Organization.id, id),

eq(Organization.id, organizationId),
and(
lte(OrganizationPresident.startTerm, date),
or(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import {
Injectable,
NotFoundException,
} from "@nestjs/common";
import { OrganizationT } from "src/drizzle/schema";
import { OrganizationT } from "@sparcs-students/api/drizzle/schema";

import { SemesterPublicService } from "src/feature/semester/semester.public.service";

import {
OrganizationRepository,
OrganizationWithPresidentT,
Expand Down Expand Up @@ -63,13 +65,17 @@ export class OrganizationPublicService {
* @description 해당 id의 organization이 없으면 404 exception을 throw 합니다.
* 가장 후임인 이유는, 새학 등 학기 중에 president가 바뀔 가능성을 고려하였습니다.
*/
async getOrganizationWithPresidentByIdAndSemester(
id: number,
async getOrganizationWithPresidentByOrganizationIdAndSemesterId(
organizationId: number,
semesterId: number,
): Promise<OrganizationWithPresidentT> {
const { endTerm } =
await this.semesterPublicService.getSemesterById(semesterId);

return this.getOrganizationWithPresidentByIdAndDate(id, endTerm);
const res = await this.getOrganizationWithPresidentByIdAndDate(
organizationId,
endTerm,
);
return res;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ export class OrganizationService {
startTerm,
endTerm,
);

// 변환 작업: OriginalResponse -> ApiOrg001ResponseOK
const organizationTypesMap = organizations.reduce((acc, curr) => {
const { organization, organizationTypeEnum } = curr;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
import { Controller, Get, Query, UsePipes } from "@nestjs/common";

import { ZodPipe } from "@sparcs-students/api/common/pipes/zod-pipe";
import {
ApiPrp001ResponseOK,
ApiPrp001RequestUrl,
apiPrp001,
ApiPrp001RequestQuery,
} from "@sparcs-students/interface/api/proposal/index";

import { ProjectProposalService } from "./project-proposal.service";

@Controller()
export class ProjectProposalController {
constructor(
private readonly projectProposalService: ProjectProposalService,
) {}

@Get(ApiPrp001RequestUrl)
@UsePipes(new ZodPipe(apiPrp001))
async getPrpanizationsBySemesterId(
@Query() query: ApiPrp001RequestQuery,
): Promise<ApiPrp001ResponseOK> {
return this.projectProposalService.getProjectProposalsForStudentsBySemesterId(
query,
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
import { Module } from "@nestjs/common";

import { DrizzleModule } from "src/drizzle/drizzle.module";
import { OrganizationModule } from "src/feature/organization/organization.module";
import { ProjectProposalRepository } from "./project-proposal.repository";
import { ProjectProposalService } from "./project-proposal.service";
import { ProjectProposalController } from "./project-proposal.controller";

@Module({
imports: [OrganizationModule, DrizzleModule],
providers: [ProjectProposalRepository, ProjectProposalService],
controllers: [ProjectProposalController],
})
export class ProjectProposalModule {}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import { Injectable, Inject } from "@nestjs/common";

import {
Agenda,
ProjectProposal,
ProjectProposalRevision,
} from "@sparcs-students/api/drizzle/schema";
import { ApiPrp001ResponseOK } from "@sparcs-students/interface/api/proposal/index";
import { AgendaAcceptedStatusE } from "@sparcs-students/interface/common/enum/meeting.enum";

import { and, eq, desc } from "drizzle-orm";

import { MySql2Database } from "drizzle-orm/mysql2";
import { DrizzleAsyncProvider } from "src/drizzle/drizzle.provider";

@Injectable()
export class ProjectProposalRepository {
constructor(
@Inject(DrizzleAsyncProvider) private readonly db: MySql2Database,
) {}

/**
* @param organizationId, semester id
* @returns 해당 동아리와 학기에 해당하는 ProjectProposals list 객체를 리턴합니다.
* @description 이미 사업계획서를 모두 심사한 후를 기준으로 하는 상태로, 일반 학생들도 볼 수 있는 상태입니다.
*/
async getProjectProposalsForStudentsByOrganizationIdAndSemesterId(
organizationId: number,
semesterId: number,
): Promise<ApiPrp001ResponseOK["projects"]> {
const res = await this.db
.select({
projectName: ProjectProposalRevision.name,
proposalId: ProjectProposal.id,
startTerm: ProjectProposalRevision.startTerm,
endTerm: ProjectProposalRevision.endTerm,
agendaAccepted: Agenda.accepted,
agendaExists: ProjectProposalRevision.agendaId,
})
.from(ProjectProposal)
.innerJoin(
ProjectProposalRevision,
eq(ProjectProposal.revisionId, ProjectProposalRevision.id),
)
.leftJoin(Agenda, eq(ProjectProposalRevision.agendaId, Agenda.id))
.where(
and(
eq(ProjectProposal.organizationId, organizationId),
eq(ProjectProposal.semesterId, semesterId),
),
);

return res.map(row => ({
name: row.projectName,
projectProposalId: row.proposalId,
startTerm: row.startTerm,
endTerm: row.endTerm,
acceptedStatus:
row.agendaExists && row.agendaAccepted
? AgendaAcceptedStatusE.Accepted
: AgendaAcceptedStatusE.Reject,
}));
}

/**
* @param organizationId, semesterId
* @returns 해당 기구 해당 학기에 해당하는 ProjectProposal의 제출연월일을 리턴합니다.
* @description 가장 최근의 제출일을 리턴합니다.
*/
async getProjectProposalSubmitDate(
organizationId: number,
semesterId: number,
): Promise<Date[]> {
const res = await this.db
.select()
.from(ProjectProposal)
.innerJoin(
ProjectProposalRevision,
eq(ProjectProposal.revisionId, ProjectProposalRevision.id),
)
.innerJoin(Agenda, eq(ProjectProposalRevision.agendaId, Agenda.id))
.where(
and(
eq(ProjectProposal.semesterId, semesterId),
eq(ProjectProposal.organizationId, organizationId),
),
)
.orderBy(desc(Agenda.submittedAt))
.limit(1);

return res.map(row => row.agenda.submittedAt);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import {
HttpException,
HttpStatus,
Injectable,
NotFoundException,
} from "@nestjs/common";

import {
ApiPrp001RequestQuery,
ApiPrp001ResponseOK,
} from "@sparcs-students/interface/api/proposal/index";
import { OrganizationPublicService } from "src/feature/organization/service/organization.public.service";
import { ProjectProposalRepository } from "./project-proposal.repository";

@Injectable()
export class ProjectProposalService {
constructor(
private readonly projectProposalRepository: ProjectProposalRepository,
private readonly organizationPublicService: OrganizationPublicService,
) {}

async getProjectProposalsForStudentsBySemesterId(
param: ApiPrp001RequestQuery,
): Promise<ApiPrp001ResponseOK> {
const organizationInfo =
await this.organizationPublicService.getOrganizationWithPresidentByOrganizationIdAndSemesterId(
param.organizationId,
param.semesterId,
);
const projectProposals =
await this.projectProposalRepository.getProjectProposalsForStudentsByOrganizationIdAndSemesterId(
param.organizationId,
param.semesterId,
);
if (projectProposals.length === 0) {
throw new NotFoundException(
`ProjectProposals with Organization ID ${param.organizationId} and semesterId ${param.semesterId} not found`,
);
}

const submitDate =
await this.projectProposalRepository.getProjectProposalSubmitDate(
param.organizationId,
param.semesterId,
);
if (submitDate.length === 0) {
throw new NotFoundException(
`ProjectProposal submitdate with Organization ID ${param.organizationId} and semesterId ${param.semesterId} not found`,
);
} else if (submitDate.length > 1) {
throw new HttpException(
"unreachable: multiple submitDate",
HttpStatus.INTERNAL_SERVER_ERROR,
);
}

return {
semesterId: param.semesterId,
organizationId: param.organizationId,
organizationName: organizationInfo.organization.name,
organizationPresidentId: organizationInfo.user.id,
organizationPresidentName: organizationInfo.user.name,
submitDate: submitDate[0],
projects: projectProposals,
};
}
}
5 changes: 4 additions & 1 deletion packages/api/src/feature/proposal/proposal.module.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,7 @@
import { Module } from "@nestjs/common";
import { ProjectProposalModule } from "./project-proposal/project-proposal.module";

@Module({})
@Module({
imports: [ProjectProposalModule],
})
export class ProposalModule {}
Loading

0 comments on commit 08e87ac

Please sign in to comment.