refactor(notes): replace AuthenticatedState with withRules, simplify handlers

refactor: add withRules wrapper to API routes

Use withRules to enforce permissions instead of manual checks.
Remove FORBIDDEN constant, simplify handlers, import withRules,
adjust GET/POST/PUT/DELETE handlers.
Centralizes auth logic.

refactor: replace manual auth checks with withRules wrapper for routes

refactor(student routes): replace manual employee checks with withRules wrapper
This commit is contained in:
2026-04-27 19:31:20 +02:00
parent e2d22ff4b3
commit 9368e68622
12 changed files with 334 additions and 486 deletions
+6 -21
View File
@@ -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<null, AuthenticatedState> = {
// #13 GET /promotions
async GET(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<AuthenticatedState>,
): Promise<Response> {
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<null, AuthenticatedState> = {
status: 201,
headers: { "content-type": "application/json" },
});
},
}),
};
@@ -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<null, AuthenticatedState> = {
// #15 GET /promotions/{idPromo}
async GET(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<AuthenticatedState>).params.idPromo,
),
)
.then((rows) => rows[0] ?? null);
if (!promo) return NOT_FOUND;
@@ -32,23 +29,21 @@ export const handler: Handlers<null, AuthenticatedState> = {
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 (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<AuthenticatedState>).params.idPromo,
),
)
.returning();
if (!updated) return NOT_FOUND;
@@ -56,24 +51,22 @@ export const handler: Handlers<null, AuthenticatedState> = {
return new Response(JSON.stringify(updated), {
headers: { "content-type": "application/json" },
});
},
}),
// #17 DELETE /promotions/{idPromo}
async DELETE(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<AuthenticatedState>).params.idPromo,
),
)
.returning();
if (!deleted) return NOT_FOUND;
return new Response(null, { status: 204 });
},
}),
};
+6 -21
View File
@@ -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<null, AuthenticatedState> = {
// #7 GET /students
async GET(
request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<null, AuthenticatedState> = {
return new Response(JSON.stringify(rows), {
headers: { "content-type": "application/json" },
});
},
}),
// #8 POST /students
async POST(
request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<null, AuthenticatedState> = {
status: 201,
headers: { "content-type": "application/json" },
});
},
}),
};
@@ -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<null, AuthenticatedState> = {
// #10 GET /students/{numEtud}
async GET(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<AuthenticatedState>).params.numEtud,
);
const student = await db
.select()
.from(students)
@@ -33,18 +27,13 @@ export const handler: Handlers<null, AuthenticatedState> = {
return new Response(JSON.stringify(student), {
headers: { "content-type": "application/json" },
});
},
}),
// #11 PUT /students/{numEtud}
async PUT(
request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<AuthenticatedState>).params.numEtud,
);
const body: { nom: string; prenom: string; idPromo: string } = await request
.json();
@@ -59,18 +48,13 @@ export const handler: Handlers<null, AuthenticatedState> = {
return new Response(JSON.stringify(updated), {
headers: { "content-type": "application/json" },
});
},
}),
// #12 DELETE /students/{numEtud}
async DELETE(
_request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<AuthenticatedState>).params.numEtud,
);
const [deleted] = await db
.delete(students)
.where(eq(students.numEtud, numEtud))
@@ -79,5 +63,5 @@ export const handler: Handlers<null, AuthenticatedState> = {
if (!deleted) return NOT_FOUND;
return new Response(null, { status: 204 });
},
}),
};
@@ -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<null, AuthenticatedState> = {
async POST(
request: Request,
context: FreshContext<AuthenticatedState>,
): Promise<Response> {
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<null, AuthenticatedState> = {
return new Response(JSON.stringify({ imported, errors }), {
headers: { "content-type": "application/json" },
});
},
}),
};