Files
PolyMPR/routes/(apps)/students/api/promotions/[idPromo].ts
T
djalim 49bcc3083a
Check Deno code / Check Deno code (push) Has been cancelled
Tests / Unit tests (push) Has been cancelled
Tests / Integration tests (push) Has been cancelled
fix: faculty users are now recognized as employees
2026-05-05 15:29:02 +02:00

174 lines
5.1 KiB
TypeScript

import { FreshContext, Handlers } from "$fresh/server.ts";
import { db } from "$root/databases/db.ts";
import {
ajustements,
enseignements,
modules,
notes,
promotions,
students,
ueModules,
ues,
} from "$root/databases/schema.ts";
import { AuthenticatedState, isEmployee } from "$root/defaults/interfaces.ts";
import { eq } from "npm:drizzle-orm@0.45.2";
const NOT_FOUND = () =>
new Response(
JSON.stringify({ error: "Ressource introuvable" }),
{ status: 404, headers: { "content-type": "application/json" } },
);
const FORBIDDEN = () => new Response(null, { status: 403 });
export const handler: Handlers<null, AuthenticatedState> = {
// #15 GET /promotions/{idPromo}
async GET(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
if (!isEmployee(context.state.session)) {
return FORBIDDEN();
}
const promo = await db
.select()
.from(promotions)
.where(eq(promotions.id, context.params.idPromo))
.then((rows) => rows[0] ?? null);
if (!promo) return NOT_FOUND();
return new Response(JSON.stringify(promo), {
headers: { "content-type": "application/json" },
});
},
// #16 PUT /promotions/{idPromo}
async PUT(
request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
if (!isEmployee(context.state.session)) {
return FORBIDDEN();
}
const body: { annee: string } = await request.json();
const [updated] = await db
.update(promotions)
.set({ annee: body.annee })
.where(eq(promotions.id, context.params.idPromo))
.returning();
if (!updated) return NOT_FOUND();
return new Response(JSON.stringify(updated), {
headers: { "content-type": "application/json" },
});
},
// #17 DELETE /promotions/{idPromo}
// Blocked if students are still assigned (409).
// Cascade: deletes linked ue_modules, enseignements, and orphaned
// modules (+ their notes) & UEs (+ their ajustements).
async DELETE(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
if (!isEmployee(context.state.session)) {
return FORBIDDEN();
}
const idPromo = context.params.idPromo;
const promo = await db
.select()
.from(promotions)
.where(eq(promotions.id, idPromo))
.then((r) => r[0] ?? null);
if (!promo) return NOT_FOUND();
// Block deletion if students are still assigned
const assignedStudents = await db
.select()
.from(students)
.where(eq(students.idPromo, idPromo))
.then((r) => r.length);
if (assignedStudents > 0) {
return new Response(
JSON.stringify({
error:
`Impossible de supprimer : ${assignedStudents} étudiant(s) encore assigné(s) à cette promotion`,
}),
{ status: 409, headers: { "content-type": "application/json" } },
);
}
await db.transaction(async (tx) => {
// Collect linked module IDs and UE IDs before deleting junction rows
const linkedUeModules = await tx
.select({ idModule: ueModules.idModule, idUE: ueModules.idUE })
.from(ueModules)
.where(eq(ueModules.idPromo, idPromo));
const linkedEns = await tx
.select({ idModule: enseignements.idModule })
.from(enseignements)
.where(eq(enseignements.idPromo, idPromo));
const moduleIds = [
...new Set([
...linkedUeModules.map((um) => um.idModule),
...linkedEns.map((e) => e.idModule),
]),
];
const ueIds = [...new Set(linkedUeModules.map((um) => um.idUE))];
// Delete junction rows that directly reference this promo
await tx.delete(ueModules).where(eq(ueModules.idPromo, idPromo));
await tx.delete(enseignements).where(eq(enseignements.idPromo, idPromo));
// Delete orphaned modules (not used by another promo) and their notes
for (const modId of moduleIds) {
const stillInUeModules = await tx
.select()
.from(ueModules)
.where(eq(ueModules.idModule, modId))
.then((r) => r.length > 0);
const stillInEns = await tx
.select()
.from(enseignements)
.where(eq(enseignements.idModule, modId))
.then((r) => r.length > 0);
if (!stillInUeModules && !stillInEns) {
await tx.delete(notes).where(eq(notes.idModule, modId));
await tx.delete(modules).where(eq(modules.id, modId));
}
}
// Delete orphaned UEs (not used by another promo) and their ajustements
for (const ueId of ueIds) {
const stillUsed = await tx
.select()
.from(ueModules)
.where(eq(ueModules.idUE, ueId))
.then((r) => r.length > 0);
if (!stillUsed) {
await tx.delete(ajustements).where(eq(ajustements.idUE, ueId));
await tx.delete(ues).where(eq(ues.id, ueId));
}
}
// Delete the promotion
await tx.delete(promotions).where(eq(promotions.id, idPromo));
});
return new Response(null, { status: 204 });
},
};