diff --git a/database-diagram.md b/database-diagram.md index 86adad1..cd0f9a4 100644 --- a/database-diagram.md +++ b/database-diagram.md @@ -99,7 +99,7 @@ erDiagram string contactPersonEmail boolean isInternational } - SCHOOL_SEMESTER_ASSISTANT{ + SCHOOL_ASSIGNMENTS{ int school FK int semester FK int user FK @@ -122,9 +122,9 @@ erDiagram SEMESTER }|--|| VEKTOR_DEPARTMENT : department SCHOOL }|--|| VEKTOR_DEPARTMENT : department SEMESTER }o--|| ASSISTENT_APPLICATION : admissionPeriod - SCHOOL_SEMESTER_ASSISTANT ||--o{ SCHOOL : schoolSemesterAssistant - SCHOOL_SEMESTER_ASSISTANT ||--o{ SEMESTER : schoolSemesterAssistant - SCHOOL_SEMESTER_ASSISTANT ||--o{ USER : shcoolSemesterAssistant + SCHOOL_ASSIGNMENTS ||--o{ SCHOOL : schoolAssignments + SCHOOL_ASSIGNMENTS ||--o{ SEMESTER : schoolAssignments + SCHOOL_ASSIGNMENTS ||--o{ USER : schoolAssignments TEAM_SEMESTER_USER ||--o{ TEAM : teamSemesterUser TEAM_SEMESTER_USER ||--o{ SEMESTER : teamSemesterUser TEAM_SEMESTER_USER ||--o{ USER : teamSemesterUser diff --git a/db/tables/applications.ts b/db/tables/applications.ts index b375563..264868c 100644 --- a/db/tables/applications.ts +++ b/db/tables/applications.ts @@ -1,6 +1,13 @@ import { mainSchema } from "@/db/tables/schema"; import { relations } from "drizzle-orm"; -import { date, integer, serial, text } from "drizzle-orm/pg-core"; +import { + boolean, + date, + integer, + primaryKey, + serial, + text, +} from "drizzle-orm/pg-core"; import { teamsTable } from "@/db/tables/teams"; import { fieldsOfStudyTable } from "./fields-of-study"; @@ -42,16 +49,24 @@ export const applicationsRelations = relations( }), ); -export const teamApplicationsTable = mainSchema.table("teamApplications", { - id: integer("id") - .primaryKey() - .references(() => applicationsTable.id), - teamId: integer("teamId") - .notNull() - .references(() => teamsTable.id), - motivationText: text("motivationText").notNull(), - biography: text("biography").notNull(), -}); +export const teamApplicationsTable = mainSchema.table( + "teamApplications", + { + id: serial("id"), + applicationParentId: integer("applicationParentId").references( + () => applicationsTable.id, + ), + teamId: integer("teamId") + .notNull() + .references(() => teamsTable.id), + motivationText: text("motivationText"), + biography: text("biography"), + teamInterest: boolean("teamInterest").notNull(), + }, + (table) => ({ + primaryKey: primaryKey({ columns: [table.id, table.applicationParentId] }), + }), +); export const teamApplicationsRelations = relations( teamApplicationsTable, diff --git a/db/tables/school-assignments.ts b/db/tables/school-assignments.ts new file mode 100644 index 0000000..5870648 --- /dev/null +++ b/db/tables/school-assignments.ts @@ -0,0 +1,42 @@ +import { relations } from "drizzle-orm"; +import { integer, primaryKey } from "drizzle-orm/pg-core"; +import { mainSchema } from "./schema"; +import { schoolsTable } from "./schools"; +import { semestersTable } from "./semesters"; +import { assistantUsersTable } from "./users"; + +export const schoolAssignmentsTable = mainSchema.table( + "schoolAssignments", + { + schoolId: integer("schoolId").references(() => schoolsTable.id), + semesterId: integer("semesterId") + .references(() => semestersTable.id) + .notNull(), + assistantUserId: integer("userId") + .references(() => assistantUsersTable.id) + .notNull(), + }, + (table) => ({ + pk: primaryKey({ + columns: [table.semesterId, table.assistantUserId], + }), + }), +); + +export const schoolAssignmentsRelations = relations( + schoolAssignmentsTable, + ({ one }) => ({ + school: one(schoolsTable, { + fields: [schoolAssignmentsTable.schoolId], + references: [schoolsTable.id], + }), + semester: one(semestersTable, { + fields: [schoolAssignmentsTable.semesterId], + references: [semestersTable.id], + }), + assistantUser: one(assistantUsersTable, { + fields: [schoolAssignmentsTable.assistantUserId], + references: [assistantUsersTable.id], + }), + }), +); diff --git a/db/tables/school-semester-assistant.ts b/db/tables/school-semester-assistant.ts deleted file mode 100644 index 5df90bd..0000000 --- a/db/tables/school-semester-assistant.ts +++ /dev/null @@ -1,33 +0,0 @@ -import { relations } from "drizzle-orm"; -import { integer } from "drizzle-orm/pg-core"; -import { mainSchema } from "./schema"; -import { schoolsTable } from "./schools"; -import { semestersTable } from "./semesters"; -import { assistantUsersTable } from "./users"; - -export const schoolSemesterAssistantsTable = mainSchema.table( - "schoolSemesterAssistant", - { - schoolId: integer("schoolId").references(() => schoolsTable.id), - semesterId: integer("semesterId").references(() => semestersTable.id), - assistantUserId: integer("userId").references(() => assistantUsersTable.id), - }, -); - -export const schoolSemesterAssistantsRelations = relations( - schoolSemesterAssistantsTable, - ({ one }) => ({ - school: one(schoolsTable, { - fields: [schoolSemesterAssistantsTable.schoolId], - references: [schoolsTable.id], - }), - semester: one(semestersTable, { - fields: [schoolSemesterAssistantsTable.semesterId], - references: [semestersTable.id], - }), - assistantUser: one(assistantUsersTable, { - fields: [schoolSemesterAssistantsTable.assistantUserId], - references: [assistantUsersTable.id], - }), - }), -); diff --git a/db/tables/schools.ts b/db/tables/schools.ts index 2084e28..71a2127 100644 --- a/db/tables/schools.ts +++ b/db/tables/schools.ts @@ -2,7 +2,7 @@ import { relations } from "drizzle-orm"; import { boolean, integer, serial, text } from "drizzle-orm/pg-core"; import { departmentsTable } from "./departments"; import { mainSchema } from "./schema"; -import { schoolSemesterAssistantsTable } from "./school-semester-assistant"; +import { schoolAssignmentsTable } from "./school-assignments"; export const schoolsTable = mainSchema.table("schools", { id: serial("id").primaryKey(), @@ -19,5 +19,5 @@ export const schoolsRelations = relations(schoolsTable, ({ one, many }) => ({ fields: [schoolsTable.departmentId], references: [departmentsTable.id], }), - semesterAssistants: many(schoolSemesterAssistantsTable), + semesterAssistants: many(schoolAssignmentsTable), })); diff --git a/db/tables/semesters.ts b/db/tables/semesters.ts index dc9e824..f287225 100644 --- a/db/tables/semesters.ts +++ b/db/tables/semesters.ts @@ -8,7 +8,7 @@ import { } from "drizzle-orm/pg-core"; import { departmentsTable } from "./departments"; import { mainSchema } from "./schema"; -import { schoolSemesterAssistantsTable } from "./school-semester-assistant"; +import { schoolAssignmentsTable } from "./school-assignments"; import { teamSemesterUsersTable } from "./team-semester-user"; export const semestersTable = mainSchema.table("semesters", { @@ -41,7 +41,7 @@ export const semestersRelations = relations( fields: [semestersTable.id], references: [semestersTable.lastSemesterId], }), - schoolAssistants: many(schoolSemesterAssistantsTable), + schoolAssistants: many(schoolAssignmentsTable), teamUsers: many(teamSemesterUsersTable), }), ); diff --git a/db/tables/users.ts b/db/tables/users.ts index b0ba0ab..f3405c4 100644 --- a/db/tables/users.ts +++ b/db/tables/users.ts @@ -5,7 +5,7 @@ import { integer, serial, text } from "drizzle-orm/pg-core"; import { expensesTable } from "@/db/tables/expenses"; import { fieldsOfStudyTable } from "@/db/tables/fields-of-study"; import { teamsTable } from "@/db/tables/teams"; -import { schoolSemesterAssistantsTable } from "./school-semester-assistant"; +import { schoolAssignmentsTable } from "./school-assignments"; import { teamSemesterUsersTable } from "./team-semester-user"; export const usersTable = mainSchema.table("users", { @@ -65,6 +65,6 @@ export const assistantUsersRelation = relations( fields: [assistantUsersTable.id], references: [usersTable.id], }), - schoolSemesters: many(schoolSemesterAssistantsTable), + schoolSemesters: many(schoolAssignmentsTable), }), ); diff --git a/src/db-access/applications.ts b/src/db-access/applications.ts index b2a7893..2fa95b2 100644 --- a/src/db-access/applications.ts +++ b/src/db-access/applications.ts @@ -3,10 +3,11 @@ import { applicationsTable, teamApplicationsTable, } from "@/db/tables/applications"; -import type { OrmResult } from "@/src/error/orm-error"; +import { type OrmResult, ormError } from "@/src/error/orm-error"; import type { NewApplication, NewTeamApplication, + NewTeamInterestApplication, } from "@/src/request-handling/applications"; import type { QueryParameters } from "@/src/request-handling/common"; import type { @@ -14,7 +15,7 @@ import type { TeamApplication, TeamKey, } from "@/src/response-handling/applications"; -import { eq, inArray } from "drizzle-orm"; +import { and, eq, inArray } from "drizzle-orm"; import { newDatabaseTransaction } from "./common"; export const selectTeamApplications = async ( @@ -24,6 +25,7 @@ export const selectTeamApplications = async ( const teamApplications = await tx .select({ id: applicationsTable.id, + applicationParentId: teamApplicationsTable.applicationParentId, teamId: teamApplicationsTable.teamId, firstName: applicationsTable.firstName, lastName: applicationsTable.lastName, @@ -34,12 +36,13 @@ export const selectTeamApplications = async ( phonenumber: applicationsTable.phonenumber, motivationText: teamApplicationsTable.motivationText, biography: teamApplicationsTable.biography, + teamInterest: teamApplicationsTable.teamInterest, submitDate: applicationsTable.submitDate, }) .from(teamApplicationsTable) .innerJoin( applicationsTable, - eq(teamApplicationsTable.id, applicationsTable.id), + eq(teamApplicationsTable.applicationParentId, applicationsTable.id), ) .limit(parameters.limit) .offset(parameters.offset); @@ -56,6 +59,7 @@ export const selectTeamApplicationsByTeamId = async ( const selectResult = await tx .select({ id: applicationsTable.id, + applicationParentId: teamApplicationsTable.applicationParentId, teamId: teamApplicationsTable.teamId, firstName: applicationsTable.firstName, lastName: applicationsTable.lastName, @@ -66,13 +70,14 @@ export const selectTeamApplicationsByTeamId = async ( phonenumber: applicationsTable.phonenumber, motivationText: teamApplicationsTable.motivationText, biography: teamApplicationsTable.biography, + teamInterest: teamApplicationsTable.teamInterest, submitDate: applicationsTable.submitDate, }) .from(teamApplicationsTable) .where(inArray(teamApplicationsTable.id, teamId)) .innerJoin( applicationsTable, - eq(teamApplicationsTable.id, applicationsTable.id), + eq(teamApplicationsTable.applicationParentId, applicationsTable.id), ) .limit(parameters.limit) .offset(parameters.offset); @@ -83,11 +88,13 @@ export const selectTeamApplicationsByTeamId = async ( export const selectTeamApplicationsById = async ( applicationIds: ApplicationKey[], + teamApplicationIds: number[], ): Promise> => { return await newDatabaseTransaction(database, async (tx) => { const selectResult = await tx .select({ - id: applicationsTable.id, + id: teamApplicationsTable.id, + applicationParentId: teamApplicationsTable.applicationParentId, teamId: teamApplicationsTable.teamId, firstName: applicationsTable.firstName, lastName: applicationsTable.lastName, @@ -98,13 +105,19 @@ export const selectTeamApplicationsById = async ( phonenumber: applicationsTable.phonenumber, motivationText: teamApplicationsTable.motivationText, biography: teamApplicationsTable.biography, + teamInterest: teamApplicationsTable.teamInterest, submitDate: applicationsTable.submitDate, }) .from(teamApplicationsTable) - .where(inArray(teamApplicationsTable.id, applicationIds)) + .where( + and( + inArray(teamApplicationsTable.applicationParentId, applicationIds), + inArray(teamApplicationsTable.id, teamApplicationIds), + ), + ) .innerJoin( applicationsTable, - eq(teamApplicationsTable.id, applicationsTable.id), + eq(teamApplicationsTable.applicationParentId, applicationsTable.id), ); return selectResult; @@ -132,10 +145,11 @@ export async function insertTeamApplication( const newTeamApplicationResult = await tx .insert(teamApplicationsTable) .values({ - id: newApplicationId, + applicationParentId: newApplicationId, teamId: teamApplication.teamId, motivationText: teamApplication.motivationText, biography: teamApplication.biography, + teamInterest: teamApplication.teamInterest, }) .returning(); @@ -145,3 +159,30 @@ export async function insertTeamApplication( }; }); } + +export async function createTeamApplicationFromAssistantApplication( + teamInterestApplication: NewTeamInterestApplication, +): Promise> { + return await newDatabaseTransaction(database, async (tx) => { + const newTeamApplicationResult = await tx + .insert(teamApplicationsTable) + .values({ + applicationParentId: teamInterestApplication.applicationParentId, + teamId: teamInterestApplication.teamId, + motivationText: teamInterestApplication.motivationText, + biography: teamInterestApplication.biography, + teamInterest: true, + }) + .returning(); + + const teamApplicationResult = await selectTeamApplicationsById( + [teamInterestApplication.applicationParentId], + [newTeamApplicationResult[0].id], + ); + if (!teamApplicationResult.success) { + throw ormError("Transaction failed", teamApplicationResult.error); + } + + return teamApplicationResult.data; + }); +} diff --git a/src/request-handling/applications.ts b/src/request-handling/applications.ts index 57e19a7..101c6e2 100644 --- a/src/request-handling/applications.ts +++ b/src/request-handling/applications.ts @@ -86,8 +86,16 @@ export const assistantApplicationToInsertParser = assistantApplicationParser .readonly(), ); +export const teamInterestParser = z.object({ + applicationParentId: serialIdParser, + teamId: teamApplicationParser.shape.teamId, + biography: teamApplicationParser.shape.biography.nullable(), + motivationText: teamApplicationParser.shape.motivationText.nullable(), +}); + export type NewApplication = z.infer; export type NewTeamApplication = z.infer; export type NewAssistantApplication = z.infer< typeof assistantApplicationToInsertParser >; +export type NewTeamInterestApplication = z.infer; diff --git a/src/response-handling/applications.ts b/src/response-handling/applications.ts index fa09a77..517a677 100644 --- a/src/response-handling/applications.ts +++ b/src/response-handling/applications.ts @@ -21,5 +21,8 @@ export const teamApplicationSelectSchema = createSelectSchema( .readonly(); export type TeamApplication = z.infer; -export type TeamApplicationKey = TeamApplication["id"]; +export type TeamApplicationKey = { + id: TeamApplication["id"]; + applicationParentId: TeamApplication["applicationParentId"]; +}; export type TeamKey = TeamApplication["teamId"]; diff --git a/src/routers/applications.ts b/src/routers/applications.ts index d2a2c33..e517927 100644 --- a/src/routers/applications.ts +++ b/src/routers/applications.ts @@ -1,10 +1,14 @@ import { + createTeamApplicationFromAssistantApplication, insertTeamApplication, selectTeamApplications, selectTeamApplicationsByTeamId, } from "@/src/db-access/applications"; import { clientError } from "@/src/error/http-errors"; -import { teamApplicationToInsertParser } from "@/src/request-handling/applications"; +import { + teamApplicationToInsertParser, + teamInterestParser, +} from "@/src/request-handling/applications"; import { toListQueryParser, toSerialIdParser, @@ -147,3 +151,52 @@ teamApplicationRouter.post("/", async (req, res, next) => { } res.status(201).json(databaseResult.data); }); + +/** + * @openapi + * /teamapplications/createFromAssistantApplication: + * post: + * tags: [teamapplications] + * summary: Make teamapplication from assistantapplication + * description: Make teamapplication from assistantapplication + * requestBody: + * required: true + * content: + * json: + * schema: + * $ref: "#/components/schemas/teamApplicationRequest" + * responses: + * 201: + * description: Successfull response + * content: + * application/json: + * schema: + * $ref: "#/components/schemas/teamApplication" + */ +teamApplicationRouter.post( + "/createFromAssistantApplication/", + async (req, res, next) => { + const teamApplicationBodyResult = teamInterestParser.safeParse(req.body); + + if (!teamApplicationBodyResult.success) { + const error = clientError( + 400, + "Invalid request format", + teamApplicationBodyResult.error, + ); + return next(error); + } + const databaseResult = await createTeamApplicationFromAssistantApplication( + teamApplicationBodyResult.data, + ); + if (!databaseResult.success) { + const error = clientError( + 400, + "Failed to execute the database command", + databaseResult.error, + ); + return next(error); + } + res.status(201).json(databaseResult.data); + }, +);