diff --git a/defaults/withRules.ts b/defaults/withRules.ts new file mode 100644 index 0000000..22e758c --- /dev/null +++ b/defaults/withRules.ts @@ -0,0 +1,108 @@ +import { FreshContext } from "$fresh/server.ts"; +import { db } from "$root/databases/db.ts"; +import { + enseignements, + rolePermissions, + users, +} from "$root/databases/schema.ts"; +import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { and, eq } from "npm:drizzle-orm@0.45.2"; + +type RuleFn = ( + req: Request, + ctx: FreshContext, +) => Promise | boolean; + +async function hasPermission( + uid: string, + permission: string, +): Promise { + const [user] = await db.select().from(users).where(eq(users.id, uid)); + if (!user || user.idRole === null) return false; + + const [rp] = await db.select().from(rolePermissions).where( + and( + eq(rolePermissions.idRole, user.idRole!), + eq(rolePermissions.idPermission, permission), + ), + ); + return !!rp; +} + +function parseNumEtud(uid: string): number { + return parseInt(uid.slice(1)); +} + +const rules = { + student_read: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "student_read"), + student_write: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "student_write"), + note_read: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "note_read"), + note_write: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "note_write"), + module_read: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "module_read"), + module_write: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "module_write"), + user_read: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "user_read"), + user_write: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "user_write"), + role_write: (_req: Request, ctx: FreshContext) => + hasPermission(ctx.state.session.uid, "role_write"), + + // Contextual rules — student accessing their own data + own_student: (_req: Request, ctx: FreshContext) => + parseNumEtud(ctx.state.session.uid) === Number(ctx.params.numEtud), + own_note: (_req: Request, ctx: FreshContext) => + parseNumEtud(ctx.state.session.uid) === Number(ctx.params.numEtud), + + // Contextual rule — teacher accessing notes for a module they teach + own_teaching_note: async ( + _req: Request, + ctx: FreshContext, + ) => { + const [row] = await db.select().from(enseignements).where( + and( + eq(enseignements.idProf, ctx.state.session.uid), + eq(enseignements.idModule, ctx.params.idModule), + ), + ); + return !!row; + }, +}; + +export type RuleName = keyof typeof rules; + +type HandlerFn = ( + req: Request, + ctx: FreshContext, +) => Promise; + +/** + * Wraps a route handler with permission checks. + * Access is granted if ANY of the provided rules passes (OR logic). + * Returns 403 if none pass. + * + * @example + * export const handler: Handlers = { + * GET: withRules(["note_read", "own_note"])(async (req, ctx) => { + * // ... + * }), + * }; + */ +export function withRules(ruleNames: RuleName[]) { + return (handler: HandlerFn): HandlerFn => { + return async (req, ctx) => { + const results = await Promise.all( + ruleNames.map((name) => rules[name](req, ctx)), + ); + if (!results.some(Boolean)) { + return new Response(null, { status: 403 }); + } + return handler(req, ctx); + }; + }; +} diff --git a/routes/(apps)/notes/api/ajustements.ts b/routes/(apps)/notes/api/ajustements.ts index 6239fb2..7d46058 100644 --- a/routes/(apps)/notes/api/ajustements.ts +++ b/routes/(apps)/notes/api/ajustements.ts @@ -1,83 +1,66 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; +import { Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { ajustements } from "$root/databases/schema.ts"; -import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { eq } from "npm:drizzle-orm@0.45.2"; -export const handler: Handlers = { +export const handler: Handlers = { // #48 GET /ajustements - async GET(request) { - try { - const url = new URL(request.url); - const numEtudParam = url.searchParams.get("numEtud"); - const idUEParam = url.searchParams.get("idUE"); + GET: withRules(["note_read"])(async (request, _context) => { + const url = new URL(request.url); + const numEtudParam = url.searchParams.get("numEtud"); + const idUEParam = url.searchParams.get("idUE"); - let query = db.select().from(ajustements).$dynamic(); + let query = db.select().from(ajustements).$dynamic(); - if (numEtudParam) { - const numEtud = parseInt(numEtudParam); - if (isNaN(numEtud)) { - return new Response("Paramètre numEtud invalide", { status: 400 }); - } - query = query.where(eq(ajustements.numEtud, numEtud)); + if (numEtudParam) { + const numEtud = parseInt(numEtudParam); + if (isNaN(numEtud)) { + return new Response("Paramètre numEtud invalide", { status: 400 }); } - - if (idUEParam) { - const idUE = parseInt(idUEParam); - if (isNaN(idUE)) { - return new Response("Paramètre idUE invalide", { status: 400 }); - } - query = query.where(eq(ajustements.idUE, idUE)); - } - - const result = await query; - - return new Response(JSON.stringify(result), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error fetching ajustements:", error); - return new Response("Failed to fetch data", { status: 500 }); + query = query.where(eq(ajustements.numEtud, numEtud)); } - }, + + if (idUEParam) { + const idUE = parseInt(idUEParam); + if (isNaN(idUE)) { + return new Response("Paramètre idUE invalide", { status: 400 }); + } + query = query.where(eq(ajustements.idUE, idUE)); + } + + const result = await query; + + return new Response(JSON.stringify(result), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }), // #49 POST /ajustements - async POST( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return new Response(null, { status: 403 }); + POST: withRules(["note_write"])(async (request, _context) => { + const body: { numEtud: number; idUE: number; valeur: number } = + await request.json(); + + if (!body.numEtud || !body.idUE || body.valeur === undefined) { + return new Response( + JSON.stringify({ error: "Champs requis: numEtud, idUE, valeur" }), + { status: 400, headers: { "content-type": "application/json" } }, + ); } - try { - const body: { numEtud: number; idUE: number; valeur: number } = - await request.json(); + const [created] = await db + .insert(ajustements) + .values({ + numEtud: body.numEtud, + idUE: body.idUE, + valeur: body.valeur, + }) + .returning(); - if (!body.numEtud || !body.idUE || body.valeur === undefined) { - return new Response( - JSON.stringify({ error: "Champs requis: numEtud, idUE, valeur" }), - { status: 400, headers: { "content-type": "application/json" } }, - ); - } - - const [created] = await db - .insert(ajustements) - .values({ - numEtud: body.numEtud, - idUE: body.idUE, - valeur: body.valeur, - }) - .returning(); - - return new Response(JSON.stringify(created), { - status: 201, - headers: { "content-type": "application/json" }, - }); - } catch (error) { - console.error("Error creating ajustement:", error); - return new Response("Failed to create ajustement", { status: 500 }); - } - }, + return new Response(JSON.stringify(created), { + status: 201, + headers: { "content-type": "application/json" }, + }); + }), }; diff --git a/routes/(apps)/notes/api/notes.ts b/routes/(apps)/notes/api/notes.ts index b7fd580..8605691 100644 --- a/routes/(apps)/notes/api/notes.ts +++ b/routes/(apps)/notes/api/notes.ts @@ -1,45 +1,41 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../databases/db.ts"; import { notes } from "../../../../databases/schema.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { // #42 GET /notes - async GET(request) { - try { - const url = new URL(request.url); - const numEtudParam = url.searchParams.get("numEtud"); - const idModule = url.searchParams.get("idModule"); + GET: withRules(["note_read", "own_note"])(async (request, _context) => { + const url = new URL(request.url); + const numEtudParam = url.searchParams.get("numEtud"); + const idModule = url.searchParams.get("idModule"); - let query = db.select().from(notes).$dynamic(); + let query = db.select().from(notes).$dynamic(); - if (numEtudParam) { - const numEtud = parseInt(numEtudParam); - if (isNaN(numEtud)) { - return new Response("Paramètre numEtud invalide", { status: 400 }); - } - query = query.where(eq(notes.numEtud, numEtud)); + if (numEtudParam) { + const numEtud = parseInt(numEtudParam); + if (isNaN(numEtud)) { + return new Response("Paramètre numEtud invalide", { status: 400 }); } - - if (idModule) { - query = query.where(eq(notes.idModule, idModule)); - } - - const result = await query; - - return new Response(JSON.stringify(result), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error fetching notes:", error); - return new Response("Failed to fetch data", { status: 500 }); + query = query.where(eq(notes.numEtud, numEtud)); } - }, + + if (idModule) { + query = query.where(eq(notes.idModule, idModule)); + } + + const result = await query; + + return new Response(JSON.stringify(result), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }), // #43 POST /notes - async POST(request) { - try { + POST: withRules(["note_write", "own_teaching_note"])( + async (request, _context) => { const body = await request.json(); const { note, numEtud, idModule } = body; @@ -62,9 +58,6 @@ export const handler: Handlers = { status: 201, headers: { "Content-Type": "application/json" }, }); - } catch (error) { - console.error("Error creating note:", error); - return new Response("Failed to create note", { status: 500 }); - } - }, + }, + ), }; diff --git a/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts b/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts index 8618366..2347601 100644 --- a/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts +++ b/routes/(apps)/notes/api/notes/[numEtud]/[idModule].ts @@ -1,104 +1,109 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../../../databases/db.ts"; import { notes } from "../../../../../../databases/schema.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { and, eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { // #45 GET /notes/:numEtud/:idModule - async GET(_request, context) { - try { - const numEtud = parseInt(context.params.numEtud); - const { idModule } = context.params; + GET: withRules(["note_read", "own_note", "own_teaching_note"])( + async (_request, context) => { + try { + const numEtud = parseInt(context.params.numEtud); + const { idModule } = context.params; - if (isNaN(numEtud)) { - return new Response( - JSON.stringify({ error: "Paramètre numEtud invalide" }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, + if (isNaN(numEtud)) { + return new Response( + JSON.stringify({ error: "Paramètre numEtud invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + const result = await db.select().from(notes).where( + and( + eq(notes.numEtud, numEtud), + eq(notes.idModule, idModule), + ), ); + + if (result.length === 0) { + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + return new Response(JSON.stringify(result[0]), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } catch (error) { + console.error("Error fetching note:", error); + return new Response("Failed to fetch data", { status: 500 }); } - - const result = await db.select().from(notes).where( - and( - eq(notes.numEtud, numEtud), - eq(notes.idModule, idModule), - ), - ); - - if (result.length === 0) { - return new Response( - JSON.stringify({ error: "Ressource introuvable" }), - { - status: 404, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - return new Response(JSON.stringify(result[0]), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error fetching note:", error); - return new Response("Failed to fetch data", { status: 500 }); - } - }, + }, + ), // #46 PUT /notes/:numEtud/:idModule - async PUT(request, context) { - try { - const numEtud = parseInt(context.params.numEtud); - const { idModule } = context.params; + PUT: withRules(["note_write", "own_teaching_note"])( + async (request, context) => { + try { + const numEtud = parseInt(context.params.numEtud); + const { idModule } = context.params; - if (isNaN(numEtud)) { - return new Response( - JSON.stringify({ error: "Paramètre numEtud invalide" }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, - ); + if (isNaN(numEtud)) { + return new Response( + JSON.stringify({ error: "Paramètre numEtud invalide" }), + { + status: 400, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + const body = await request.json(); + const { note } = body; + + if (note === undefined) { + return new Response("Champ 'note' manquant", { status: 400 }); + } + + const result = await db.update(notes).set({ note }).where( + and( + eq(notes.numEtud, numEtud), + eq(notes.idModule, idModule), + ), + ).returning(); + + if (result.length === 0) { + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { + status: 404, + headers: { "Content-Type": "application/json" }, + }, + ); + } + + return new Response(JSON.stringify(result[0]), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + } catch (error) { + console.error("Error updating note:", error); + return new Response("Failed to update note", { status: 500 }); } - - const body = await request.json(); - const { note } = body; - - if (note === undefined) { - return new Response("Champ 'note' manquant", { status: 400 }); - } - - const result = await db.update(notes).set({ note }).where( - and( - eq(notes.numEtud, numEtud), - eq(notes.idModule, idModule), - ), - ).returning(); - - if (result.length === 0) { - return new Response( - JSON.stringify({ error: "Ressource introuvable" }), - { - status: 404, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - return new Response(JSON.stringify(result[0]), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error updating note:", error); - return new Response("Failed to update note", { status: 500 }); - } - }, + }, + ), // #47 DELETE /notes/:numEtud/:idModule - async DELETE(_request, context) { + DELETE: withRules(["note_write"])(async (_request, context) => { try { const numEtud = parseInt(context.params.numEtud); const { idModule } = context.params; @@ -135,5 +140,5 @@ export const handler: Handlers = { console.error("Error deleting note:", error); return new Response("Failed to delete note", { status: 500 }); } - }, + }), }; diff --git a/routes/(apps)/notes/api/ue-modules.ts b/routes/(apps)/notes/api/ue-modules.ts index 1a825a6..6ae70cc 100644 --- a/routes/(apps)/notes/api/ue-modules.ts +++ b/routes/(apps)/notes/api/ue-modules.ts @@ -1,72 +1,63 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../databases/db.ts"; import { ueModules } from "../../../../databases/schema.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { and, eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { // #37 GET /ue-modules - async GET(request) { - try { - const url = new URL(request.url); - const idPromo = url.searchParams.get("idPromo"); - const idUEParam = url.searchParams.get("idUE"); + GET: withRules(["note_read"])(async (request, _context) => { + const url = new URL(request.url); + const idPromo = url.searchParams.get("idPromo"); + const idUEParam = url.searchParams.get("idUE"); - const idUE = idUEParam ? parseInt(idUEParam) : null; + const idUE = idUEParam ? parseInt(idUEParam) : null; - if (idUEParam && isNaN(idUE!)) { - return new Response("Paramètre idUE invalide", { status: 400 }); - } - - const result = await db.select().from(ueModules).where( - and( - idPromo ? eq(ueModules.idPromo, idPromo) : undefined, - idUE ? eq(ueModules.idUE, idUE) : undefined, - ), - ); - - return new Response(JSON.stringify(result), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error fetching UE-modules:", error); - return new Response("Failed to fetch data", { status: 500 }); + if (idUEParam && isNaN(idUE!)) { + return new Response("Paramètre idUE invalide", { status: 400 }); } - }, + + const result = await db.select().from(ueModules).where( + and( + idPromo ? eq(ueModules.idPromo, idPromo) : undefined, + idUE ? eq(ueModules.idUE, idUE) : undefined, + ), + ); + + return new Response(JSON.stringify(result), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }), // #38 POST /ue-modules - async POST(request) { - try { - const body = await request.json(); - const { idModule, idUE, idPromo, coeff } = body; + POST: withRules(["note_write"])(async (request, _context) => { + const body = await request.json(); + const { idModule, idUE, idPromo, coeff } = body; - if (!idModule || !idUE || !idPromo || coeff === undefined) { - return new Response( - "Champs 'idModule', 'idUE', 'idPromo' et 'coeff' requis", - { status: 400 }, - ); - } - - if (typeof coeff !== "number" || coeff < 0) { - return new Response("Champ 'coeff' doit être un nombre >= 0", { - status: 400, - }); - } - - const result = await db.insert(ueModules).values({ - idModule, - idUE, - idPromo, - coeff, - }).returning(); - - return new Response(JSON.stringify(result[0]), { - status: 201, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error creating UE-module:", error); - return new Response("Failed to create UE-module", { status: 500 }); + if (!idModule || !idUE || !idPromo || coeff === undefined) { + return new Response( + "Champs 'idModule', 'idUE', 'idPromo' et 'coeff' requis", + { status: 400 }, + ); } - }, + + if (typeof coeff !== "number" || coeff < 0) { + return new Response("Champ 'coeff' doit être un nombre >= 0", { + status: 400, + }); + } + + const result = await db.insert(ueModules).values({ + idModule, + idUE, + idPromo, + coeff, + }).returning(); + + return new Response(JSON.stringify(result[0]), { + status: 201, + headers: { "Content-Type": "application/json" }, + }); + }), }; diff --git a/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts b/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts index f447f12..5bf0c9b 100644 --- a/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts +++ b/routes/(apps)/notes/api/ue-modules/[idModule]/[idUE]/[idPromo].ts @@ -2,6 +2,7 @@ import { FreshContext, Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { ueModules } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { and, eq } from "npm:drizzle-orm@0.45.2"; const NOT_FOUND = new Response( @@ -9,8 +10,6 @@ const NOT_FOUND = new Response( { status: 404, headers: { "content-type": "application/json" } }, ); -const FORBIDDEN = new Response(null, { status: 403 }); - const BAD_REQUEST = new Response( JSON.stringify({ error: "Paramètres invalides" }), { status: 400, headers: { "content-type": "application/json" } }, @@ -18,29 +17,24 @@ const BAD_REQUEST = new Response( export const handler: Handlers = { // #39 GET /ue-modules/{idModule}/{idUE}/{idPromo} - async GET( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } + GET: withRules(["note_read"])(async (_request, context) => { + const { idModule, idPromo } = (context as FreshContext) + .params; + const idUE = Number( + (context as FreshContext).params.idUE, + ); - const idModule = context.params.idModule; - const idUE = Number(context.params.idUE); - const idPromo = context.params.idPromo; - - if (isNaN(idUE)) { - return BAD_REQUEST; - } + if (isNaN(idUE)) return BAD_REQUEST; const ueModuleAssociation = await db .select() .from(ueModules) .where( - eq(ueModules.idModule, idModule), - eq(ueModules.idUE, idUE), - eq(ueModules.idPromo, idPromo), + and( + eq(ueModules.idModule, idModule), + eq(ueModules.idUE, idUE), + eq(ueModules.idPromo, idPromo), + ), ) .then((rows) => rows[0] ?? null); @@ -49,24 +43,17 @@ export const handler: Handlers = { return new Response(JSON.stringify(ueModuleAssociation), { headers: { "content-type": "application/json" }, }); - }, + }), // #40 PUT /ue-modules/{idModule}/{idUE}/{idPromo} - async PUT( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } + PUT: withRules(["note_write"])(async (request, context) => { + const { idModule, idPromo } = (context as FreshContext) + .params; + const idUE = Number( + (context as FreshContext).params.idUE, + ); - const idModule = context.params.idModule; - const idUE = Number(context.params.idUE); - const idPromo = context.params.idPromo; - - if (isNaN(idUE)) { - return BAD_REQUEST; - } + if (isNaN(idUE)) return BAD_REQUEST; const body: { coeff: number } = await request.json(); @@ -98,28 +85,19 @@ export const handler: Handlers = { idPromo: updated.idPromo, coeff: updated.coeff, }), - { - headers: { "content-type": "application/json" }, - }, + { headers: { "content-type": "application/json" } }, ); - }, + }), // #41 DELETE /ue-modules/{idModule}/{idUE}/{idPromo} - async DELETE( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } + DELETE: withRules(["note_write"])(async (_request, context) => { + const { idModule, idPromo } = (context as FreshContext) + .params; + const idUE = Number( + (context as FreshContext).params.idUE, + ); - const idModule = context.params.idModule; - const idUE = Number(context.params.idUE); - const idPromo = context.params.idPromo; - - if (isNaN(idUE)) { - return BAD_REQUEST; - } + if (isNaN(idUE)) return BAD_REQUEST; const [deleted] = await db .delete(ueModules) @@ -135,5 +113,5 @@ export const handler: Handlers = { if (!deleted) return NOT_FOUND; return new Response(null, { status: 204 }); - }, + }), }; diff --git a/routes/(apps)/notes/api/ues.ts b/routes/(apps)/notes/api/ues.ts index 92242da..9be4c26 100644 --- a/routes/(apps)/notes/api/ues.ts +++ b/routes/(apps)/notes/api/ues.ts @@ -1,42 +1,33 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../databases/db.ts"; import { ues } from "../../../../databases/schema.ts"; +import { withRules } from "$root/defaults/withRules.ts"; export const handler: Handlers = { // #32 GET /ues - async GET() { - try { - const result = await db.select().from(ues); + GET: withRules(["note_read"])(async (_request, _context) => { + const result = await db.select().from(ues); - return new Response(JSON.stringify(result), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error fetching UEs:", error); - return new Response("Failed to fetch data", { status: 500 }); - } - }, + return new Response(JSON.stringify(result), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }), // #33 POST /ues - async POST(request) { - try { - const body = await request.json(); - const { nom } = body; + POST: withRules(["note_write"])(async (request, _context) => { + const body = await request.json(); + const { nom } = body; - if (!nom || !nom.trim()) { - return new Response("Champ 'nom' manquant", { status: 400 }); - } - - const result = await db.insert(ues).values({ nom }).returning(); - - return new Response(JSON.stringify(result[0]), { - status: 201, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error creating UE:", error); - return new Response("Failed to create UE", { status: 500 }); + if (!nom || !nom.trim()) { + return new Response("Champ 'nom' manquant", { status: 400 }); } - }, + + const result = await db.insert(ues).values({ nom }).returning(); + + return new Response(JSON.stringify(result[0]), { + status: 201, + headers: { "Content-Type": "application/json" }, + }); + }), }; diff --git a/routes/(apps)/notes/api/ues/[idUE].ts b/routes/(apps)/notes/api/ues/[idUE].ts index c8f586f..4f5a6ae 100644 --- a/routes/(apps)/notes/api/ues/[idUE].ts +++ b/routes/(apps)/notes/api/ues/[idUE].ts @@ -1,122 +1,90 @@ import { Handlers } from "$fresh/server.ts"; import { db } from "../../../../../databases/db.ts"; import { ues } from "../../../../../databases/schema.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { - // # 34 GET /ues/:idUE - async GET(_request, context) { - try { - const idUE = parseInt(context.params.idUE); + // #34 GET /ues/:idUE + GET: withRules(["note_read"])(async (_request, context) => { + const idUE = parseInt(context.params.idUE); - if (isNaN(idUE)) { - return new Response( - JSON.stringify({ error: "Paramètre idUE invalide" }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - const result = await db.select().from(ues).where(eq(ues.id, idUE)); - - if (result.length === 0) { - return new Response( - JSON.stringify({ error: "Ressource introuvable" }), - { - status: 404, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - return new Response(JSON.stringify(result[0]), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error fetching UE:", error); - return new Response("Failed to fetch data", { status: 500 }); + if (isNaN(idUE)) { + return new Response( + JSON.stringify({ error: "Paramètre idUE invalide" }), + { status: 400, headers: { "Content-Type": "application/json" } }, + ); } - }, + + const result = await db.select().from(ues).where(eq(ues.id, idUE)); + + if (result.length === 0) { + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { status: 404, headers: { "Content-Type": "application/json" } }, + ); + } + + return new Response(JSON.stringify(result[0]), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }), // #35 PUT /ues/:idUE - async PUT(request, context) { - try { - const idUE = parseInt(context.params.idUE); + PUT: withRules(["note_write"])(async (request, context) => { + const idUE = parseInt(context.params.idUE); - if (isNaN(idUE)) { - return new Response( - JSON.stringify({ error: "Paramètre idUE invalide" }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - const body = await request.json(); - const { nom } = body; - - if (!nom) { - return new Response("Champ 'nom' manquant", { status: 400 }); - } - - const result = await db.update(ues).set({ nom }).where(eq(ues.id, idUE)) - .returning(); - - if (result.length === 0) { - return new Response( - JSON.stringify({ error: "Ressource introuvable" }), - { - status: 404, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - return new Response(JSON.stringify(result[0]), { - status: 200, - headers: { "Content-Type": "application/json" }, - }); - } catch (error) { - console.error("Error updating UE:", error); - return new Response("Failed to update UE", { status: 500 }); + if (isNaN(idUE)) { + return new Response( + JSON.stringify({ error: "Paramètre idUE invalide" }), + { status: 400, headers: { "Content-Type": "application/json" } }, + ); } - }, + + const body = await request.json(); + const { nom } = body; + + if (!nom) { + return new Response("Champ 'nom' manquant", { status: 400 }); + } + + const result = await db.update(ues).set({ nom }).where(eq(ues.id, idUE)) + .returning(); + + if (result.length === 0) { + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { status: 404, headers: { "Content-Type": "application/json" } }, + ); + } + + return new Response(JSON.stringify(result[0]), { + status: 200, + headers: { "Content-Type": "application/json" }, + }); + }), // #36 DELETE /ues/:idUE - async DELETE(_request, context) { - try { - const idUE = parseInt(context.params.idUE); + DELETE: withRules(["note_write"])(async (_request, context) => { + const idUE = parseInt(context.params.idUE); - if (isNaN(idUE)) { - return new Response( - JSON.stringify({ error: "Paramètre idUE invalide" }), - { - status: 400, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - const result = await db.delete(ues).where(eq(ues.id, idUE)).returning(); - - if (result.length === 0) { - return new Response( - JSON.stringify({ error: "Ressource introuvable" }), - { - status: 404, - headers: { "Content-Type": "application/json" }, - }, - ); - } - - return new Response(null, { status: 204 }); - } catch (error) { - console.error("Error deleting UE:", error); - return new Response("Failed to delete UE", { status: 500 }); + if (isNaN(idUE)) { + return new Response( + JSON.stringify({ error: "Paramètre idUE invalide" }), + { status: 400, headers: { "Content-Type": "application/json" } }, + ); } - }, + + const result = await db.delete(ues).where(eq(ues.id, idUE)).returning(); + + if (result.length === 0) { + return new Response( + JSON.stringify({ error: "Ressource introuvable" }), + { status: 404, headers: { "Content-Type": "application/json" } }, + ); + } + + return new Response(null, { status: 204 }); + }), }; diff --git a/routes/(apps)/students/api/promotions.ts b/routes/(apps)/students/api/promotions.ts index 8e87820..e713148 100644 --- a/routes/(apps)/students/api/promotions.ts +++ b/routes/(apps)/students/api/promotions.ts @@ -1,35 +1,20 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; +import { Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { promotions } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; export const handler: Handlers = { // #13 GET /promotions - async GET( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return new Response(JSON.stringify([]), { - headers: { "content-type": "application/json" }, - }); - } - + GET: withRules(["student_read"])(async (_request, _context) => { const rows = await db.select().from(promotions); return new Response(JSON.stringify(rows), { headers: { "content-type": "application/json" }, }); - }, + }), // #14 POST /promotions - async POST( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return new Response(null, { status: 403 }); - } - + POST: withRules(["student_write"])(async (request, _context) => { const body: { idPromo: string; annee: string } = await request.json(); if (!body.idPromo || !body.annee) { @@ -45,5 +30,5 @@ export const handler: Handlers = { status: 201, headers: { "content-type": "application/json" }, }); - }, + }), }; diff --git a/routes/(apps)/students/api/promotions/[idPromo].ts b/routes/(apps)/students/api/promotions/[idPromo].ts index a206d3a..15030c4 100644 --- a/routes/(apps)/students/api/promotions/[idPromo].ts +++ b/routes/(apps)/students/api/promotions/[idPromo].ts @@ -2,6 +2,7 @@ import { FreshContext, Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { promotions } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { eq } from "npm:drizzle-orm@0.45.2"; const NOT_FOUND = new Response( @@ -9,22 +10,18 @@ const NOT_FOUND = new Response( { status: 404, headers: { "content-type": "application/json" } }, ); -const FORBIDDEN = new Response(null, { status: 403 }); - export const handler: Handlers = { // #15 GET /promotions/{idPromo} - async GET( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } - + GET: withRules(["student_read"])(async (_request, context) => { const promo = await db .select() .from(promotions) - .where(eq(promotions.id, context.params.idPromo)) + .where( + eq( + promotions.id, + (context as FreshContext).params.idPromo, + ), + ) .then((rows) => rows[0] ?? null); if (!promo) return NOT_FOUND; @@ -32,23 +29,21 @@ export const handler: Handlers = { return new Response(JSON.stringify(promo), { headers: { "content-type": "application/json" }, }); - }, + }), // #16 PUT /promotions/{idPromo} - async PUT( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } - + PUT: withRules(["student_write"])(async (request, context) => { const body: { annee: string } = await request.json(); const [updated] = await db .update(promotions) .set({ annee: body.annee }) - .where(eq(promotions.id, context.params.idPromo)) + .where( + eq( + promotions.id, + (context as FreshContext).params.idPromo, + ), + ) .returning(); if (!updated) return NOT_FOUND; @@ -56,24 +51,22 @@ export const handler: Handlers = { return new Response(JSON.stringify(updated), { headers: { "content-type": "application/json" }, }); - }, + }), // #17 DELETE /promotions/{idPromo} - async DELETE( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } - + DELETE: withRules(["student_write"])(async (_request, context) => { const [deleted] = await db .delete(promotions) - .where(eq(promotions.id, context.params.idPromo)) + .where( + eq( + promotions.id, + (context as FreshContext).params.idPromo, + ), + ) .returning(); if (!deleted) return NOT_FOUND; return new Response(null, { status: 204 }); - }, + }), }; diff --git a/routes/(apps)/students/api/students.ts b/routes/(apps)/students/api/students.ts index 65ed62d..261a5da 100644 --- a/routes/(apps)/students/api/students.ts +++ b/routes/(apps)/students/api/students.ts @@ -1,21 +1,13 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; +import { Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { students } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { eq } from "npm:drizzle-orm@0.45.2"; export const handler: Handlers = { // #7 GET /students - async GET( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return new Response(JSON.stringify([]), { - headers: { "content-type": "application/json" }, - }); - } - + GET: withRules(["student_read"])(async (request, _context) => { const url = new URL(request.url); const idPromo = url.searchParams.get("idPromo"); @@ -26,17 +18,10 @@ export const handler: Handlers = { return new Response(JSON.stringify(rows), { headers: { "content-type": "application/json" }, }); - }, + }), // #8 POST /students - async POST( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return new Response(null, { status: 403 }); - } - + POST: withRules(["student_write"])(async (request, _context) => { const body: { numEtud: number; nom: string; @@ -57,5 +42,5 @@ export const handler: Handlers = { status: 201, headers: { "content-type": "application/json" }, }); - }, + }), }; diff --git a/routes/(apps)/students/api/students/[numEtud].ts b/routes/(apps)/students/api/students/[numEtud].ts index 3d92371..b9fb8ad 100644 --- a/routes/(apps)/students/api/students/[numEtud].ts +++ b/routes/(apps)/students/api/students/[numEtud].ts @@ -2,6 +2,7 @@ import { FreshContext, Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { students } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; import { eq } from "npm:drizzle-orm@0.45.2"; const NOT_FOUND = new Response( @@ -9,19 +10,12 @@ const NOT_FOUND = new Response( { status: 404, headers: { "content-type": "application/json" } }, ); -const FORBIDDEN = new Response(null, { status: 403 }); - export const handler: Handlers = { // #10 GET /students/{numEtud} - async GET( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } - - const numEtud = Number(context.params.numEtud); + GET: withRules(["student_read"])(async (_request, context) => { + const numEtud = Number( + (context as FreshContext).params.numEtud, + ); const student = await db .select() .from(students) @@ -33,18 +27,13 @@ export const handler: Handlers = { return new Response(JSON.stringify(student), { headers: { "content-type": "application/json" }, }); - }, + }), // #11 PUT /students/{numEtud} - async PUT( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } - - const numEtud = Number(context.params.numEtud); + PUT: withRules(["student_write"])(async (request, context) => { + const numEtud = Number( + (context as FreshContext).params.numEtud, + ); const body: { nom: string; prenom: string; idPromo: string } = await request .json(); @@ -59,18 +48,13 @@ export const handler: Handlers = { return new Response(JSON.stringify(updated), { headers: { "content-type": "application/json" }, }); - }, + }), // #12 DELETE /students/{numEtud} - async DELETE( - _request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return FORBIDDEN; - } - - const numEtud = Number(context.params.numEtud); + DELETE: withRules(["student_write"])(async (_request, context) => { + const numEtud = Number( + (context as FreshContext).params.numEtud, + ); const [deleted] = await db .delete(students) .where(eq(students.numEtud, numEtud)) @@ -79,5 +63,5 @@ export const handler: Handlers = { if (!deleted) return NOT_FOUND; return new Response(null, { status: 204 }); - }, + }), }; diff --git a/routes/(apps)/students/api/students/import-csv.ts b/routes/(apps)/students/api/students/import-csv.ts index 1e233a0..12778b0 100644 --- a/routes/(apps)/students/api/students/import-csv.ts +++ b/routes/(apps)/students/api/students/import-csv.ts @@ -1,18 +1,12 @@ -import { FreshContext, Handlers } from "$fresh/server.ts"; +import { Handlers } from "$fresh/server.ts"; import { db } from "$root/databases/db.ts"; import { students } from "$root/databases/schema.ts"; import { AuthenticatedState } from "$root/defaults/interfaces.ts"; +import { withRules } from "$root/defaults/withRules.ts"; // #9 POST /students/import-csv export const handler: Handlers = { - async POST( - request: Request, - context: FreshContext, - ): Promise { - if (context.state.session.eduPersonPrimaryAffiliation !== "employee") { - return new Response(null, { status: 403 }); - } - + POST: withRules(["student_write"])(async (request, _context) => { const formData = await request.formData(); const file = formData.get("file") as File | null; const idPromo = formData.get("idPromo") as string | null; @@ -60,5 +54,5 @@ export const handler: Handlers = { return new Response(JSON.stringify({ imported, errors }), { headers: { "content-type": "application/json" }, }); - }, + }), };