Checkpoint: Ajout des permissions de gestion des attestations pour les formateurs et super-formateurs : création de formateurProcedure, fonction canAccessSequence pour vérifier les accès par séquence, modification des procédures uploadDocument, deleteDocument, sendAttestation et sendAllAttestations avec vérifications de permissions

This commit is contained in:
Manus
2026-02-02 09:53:28 -05:00
parent fadadf0d7d
commit 42d57c6e7c
3 changed files with 83 additions and 11 deletions

View File

@@ -25,6 +25,14 @@ const adminProcedure = protectedProcedure.use(({ ctx, next }) => {
return next({ ctx });
});
// Procédure pour formateurs, super-formateurs et admins
const formateurProcedure = protectedProcedure.use(({ ctx, next }) => {
if (!['admin', 'formateur', 'super_formateur'].includes(ctx.user.role)) {
throw new TRPCError({ code: 'FORBIDDEN', message: 'Accès réservé aux formateurs et administrateurs' });
}
return next({ ctx });
});
export const appRouter = router({
system: systemRouter,
auth: router({
@@ -2713,14 +2721,28 @@ export const appRouter = router({
}),
// Uploader un document d'attestation
uploadDocument: adminProcedure
uploadDocument: formateurProcedure
.input(z.object({
inscriptionId: z.number(),
documentUrl: z.string(),
documentS3Key: z.string(),
}))
.mutation(async ({ input, ctx }) => {
const { uploadAttestationDocument } = await import("./gestionAttestationsDb");
const { uploadAttestationDocument, canAccessSequence } = await import("./gestionAttestationsDb");
const { getDb } = await import("./db");
const { inscriptions } = await import("../drizzle/schema");
const { eq } = await import("drizzle-orm");
// Récupérer la séquence de l'inscription
const db = await getDb();
if (!db) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Database not available' });
const inscription = await db.select({ sequenceId: inscriptions.sequenceId }).from(inscriptions).where(eq(inscriptions.id, input.inscriptionId)).limit(1);
if (inscription.length === 0) throw new TRPCError({ code: 'NOT_FOUND', message: 'Inscription not found' });
// Vérifier l'accès
const hasAccess = await canAccessSequence(ctx.user.id, ctx.user.role, inscription[0].sequenceId);
if (!hasAccess) throw new TRPCError({ code: 'FORBIDDEN', message: 'Accès refusé à cette séquence' });
return uploadAttestationDocument(
input.inscriptionId,
input.documentUrl,
@@ -2730,15 +2752,29 @@ export const appRouter = router({
}),
// Supprimer un document d'attestation
deleteDocument: adminProcedure
deleteDocument: formateurProcedure
.input(z.object({ attestationId: z.number() }))
.mutation(async ({ input }) => {
const { deleteAttestationDocument } = await import("./gestionAttestationsDb");
.mutation(async ({ input, ctx }) => {
const { deleteAttestationDocument, canAccessSequence } = await import("./gestionAttestationsDb");
const { getDb } = await import("./db");
const { attestations } = await import("../drizzle/schema");
const { eq } = await import("drizzle-orm");
// Récupérer la séquence de l'attestation
const db = await getDb();
if (!db) throw new TRPCError({ code: 'INTERNAL_SERVER_ERROR', message: 'Database not available' });
const attestation = await db.select({ sequenceId: attestations.sequenceId }).from(attestations).where(eq(attestations.id, input.attestationId)).limit(1);
if (attestation.length === 0) throw new TRPCError({ code: 'NOT_FOUND', message: 'Attestation not found' });
// Vérifier l'accès
const hasAccess = await canAccessSequence(ctx.user.id, ctx.user.role, attestation[0].sequenceId);
if (!hasAccess) throw new TRPCError({ code: 'FORBIDDEN', message: 'Accès refusé à cette séquence' });
return deleteAttestationDocument(input.attestationId);
}),
// Envoyer une attestation par email
sendAttestation: adminProcedure
sendAttestation: formateurProcedure
.input(z.object({
attestationId: z.number(),
sequenceId: z.number(),
@@ -2749,11 +2785,15 @@ export const appRouter = router({
formationNom: z.string(),
pdfUrl: z.string(),
}))
.mutation(async ({ input }) => {
.mutation(async ({ input, ctx }) => {
const { sendAttestationEmail } = await import("./emailService");
const { markAttestationAsSent } = await import("./gestionAttestationsDb");
const { markAttestationAsSent, canAccessSequence } = await import("./gestionAttestationsDb");
const { enregistrerEnvoiAttestation } = await import("./attestationsDb");
// Vérifier l'accès à la séquence
const hasAccess = await canAccessSequence(ctx.user.id, ctx.user.role, input.sequenceId);
if (!hasAccess) throw new TRPCError({ code: 'FORBIDDEN', message: 'Accès refusé à cette séquence' });
try {
// Envoyer l'email
await sendAttestationEmail({
@@ -2790,7 +2830,7 @@ export const appRouter = router({
}),
// Envoyer toutes les attestations d'une formation en masse
sendAllAttestations: adminProcedure
sendAllAttestations: formateurProcedure
.input(z.object({ formationId: z.number() }))
.mutation(async ({ input }) => {
const { getApprenantsWithAttestationStatus } = await import("./gestionAttestationsDb");