Checkpoint: Phase 1 : Système d'attestations de formation automatiques

 Infrastructure backend complète :
- Tables attestations et configAttestation en base de données
- Service de génération PDF (attestationService.ts) avec PDFKit
- Procédures tRPC pour générer, récupérer et configurer les attestations
- Stockage S3 des PDF générés

 Interface d'administration :
- Page de configuration des attestations (/admin/attestations)
- Personnalisation du texte avec variables dynamiques
- Champs pour signataire (nom et fonction)
- Placeholders pour logo et signature (à implémenter en phase 2)

📋 À faire en Phase 2 :
- Upload des images (logo/signature) vers S3
- Génération automatique après validation de présence
- Envoi automatique par email aux apprenants
- Espace apprenant pour télécharger les attestations
This commit is contained in:
Manus
2026-01-07 06:35:12 -05:00
parent 29b35fceaa
commit 2eb352206f
14 changed files with 3098 additions and 0 deletions

View File

@@ -0,0 +1,226 @@
{
"query": "SELECT \n type,\n emailDestinataire,\n sujet,\n dateEnvoi,\n statut,\n sequenceId,\n apprenantId\nFROM logsNotifications \nWHERE type = 'remerciement'\nORDER BY dateEnvoi DESC\nLIMIT 50;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT \n type,\n emailDestinataire,\n sujet,\n dateEnvoi,\n statut,\n sequenceId,\n apprenantId\nFROM logsNotifications \nWHERE type = 'remerciement'\nORDER BY dateEnvoi DESC\nLIMIT 50;",
"rows": [
{
"type": "remerciement",
"emailDestinataire": "alerte2.test@example.com",
"sujet": "Merci pour votre participation - Formation manager Itinova",
"dateEnvoi": "2025-12-18 11:23:55",
"statut": "success",
"sequenceId": "30001",
"apprenantId": "90002"
},
{
"type": "remerciement",
"emailDestinataire": "test.alerte@example.com",
"sujet": "Merci pour votre participation - Formation manager Itinova",
"dateEnvoi": "2025-12-18 09:42:30",
"statut": "success",
"sequenceId": "30001",
"apprenantId": "90001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation manager Itinova",
"dateEnvoi": "2025-12-18 09:42:27",
"statut": "success",
"sequenceId": "30001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 21:16:59",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 21:16:32",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 21:14:32",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 21:14:25",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 21:00:43",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 21:00:40",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:34:28",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:34:00",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:33:28",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:33:21",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:26:15",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:26:13",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:25:51",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:25:49",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:25:31",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:25:28",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:25:14",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:25:12",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "o.pareige@itinova.org",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:24:58",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "240001"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:24:55",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
},
{
"type": "remerciement",
"emailDestinataire": "opareige@gmail.com",
"sujet": "Merci pour votre participation - Formation Jetski",
"dateEnvoi": "2025-12-15 20:24:49",
"statut": "success",
"sequenceId": "330001",
"apprenantId": "1"
}
],
"messages": [],
"stdout": "type\temailDestinataire\tsujet\tdateEnvoi\tstatut\tsequenceId\tapprenantId\nremerciement\talerte2.test@example.com\tMerci pour votre participation - Formation manager Itinova\t2025-12-18 11:23:55\tsuccess\t30001\t90002\nremerciement\ttest.alerte@example.com\tMerci pour votre participation - Formation manager Itinova\t2025-12-18 09:42:30\tsuccess\t30001\t90001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation manager Itinova\t2025-12-18 09:42:27\tsuccess\t30001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:16:59\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:16:32\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:14:32\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:14:25\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:00:43\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 21:00:40\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:34:28\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:34:00\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:33:28\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:33:21\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:26:15\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:26:13\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:51\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:49\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:31\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:28\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:14\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:25:12\tsuccess\t330001\t1\nremerciement\to.pareige@itinova.org\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:24:58\tsuccess\t330001\t240001\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:24:55\tsuccess\t330001\t1\nremerciement\topareige@gmail.com\tMerci pour votre participation - Formation Jetski\t2025-12-15 20:24:49\tsuccess\t330001\t1\n",
"stderr": "",
"execution_time_ms": 168
}

View File

@@ -0,0 +1,24 @@
{
"query": "SELECT \n emailDestinataire,\n sequenceId,\n COUNT(*) as nb_envois,\n MIN(dateEnvoi) as premier_envoi,\n MAX(dateEnvoi) as dernier_envoi\nFROM logsNotifications \nWHERE type = 'remerciement' AND statut = 'success'\nGROUP BY emailDestinataire, sequenceId\nHAVING COUNT(*) > 1\nORDER BY nb_envois DESC, dernier_envoi DESC;",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute SELECT \n emailDestinataire,\n sequenceId,\n COUNT(*) as nb_envois,\n MIN(dateEnvoi) as premier_envoi,\n MAX(dateEnvoi) as dernier_envoi\nFROM logsNotifications \nWHERE type = 'remerciement' AND statut = 'success'\nGROUP BY emailDestinataire, sequenceId\nHAVING COUNT(*) > 1\nORDER BY nb_envois DESC, dernier_envoi DESC;",
"rows": [
{
"emailDestinataire": "opareige@gmail.com",
"sequenceId": "330001",
"nb_envois": "11",
"premier_envoi": "2025-12-15 20:24:49",
"dernier_envoi": "2025-12-15 21:16:32"
},
{
"emailDestinataire": "o.pareige@itinova.org",
"sequenceId": "330001",
"nb_envois": "10",
"premier_envoi": "2025-12-15 20:24:58",
"dernier_envoi": "2025-12-15 21:16:59"
}
],
"messages": [],
"stdout": "emailDestinataire\tsequenceId\tnb_envois\tpremier_envoi\tdernier_envoi\nopareige@gmail.com\t330001\t11\t2025-12-15 20:24:49\t2025-12-15 21:16:32\no.pareige@itinova.org\t330001\t10\t2025-12-15 20:24:58\t2025-12-15 21:16:59\n",
"stderr": "",
"execution_time_ms": 61
}

View File

@@ -0,0 +1,9 @@
{
"query": "CREATE TABLE IF NOT EXISTS `attestations` (\n `id` int AUTO_INCREMENT NOT NULL,\n `inscriptionId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `s3Key` varchar(500) NOT NULL,\n `pdfUrl` varchar(1000) NOT NULL,\n `dateGeneration` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `emailEnvoye` boolean NOT NULL DEFAULT false,\n `dateEnvoiEmail` timestamp NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT `attestations_id` PRIMARY KEY(`id`)\n);\n\nCREATE TABLE IF NOT EXISTS `configAttestation` (\n `id` int AUTO_INCREMENT NOT NULL,\n `logoS3Key` varchar(500),\n `logoUrl` varchar(1000),\n `signatureS3Key` varchar(500),\n `signatureUrl` varchar(1000),\n `nomSignataire` varchar(255),\n `fonctionSignataire` varchar(255),\n `texteAttestation` text,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `configAttestation_id` PRIMARY KEY(`id`)\n);",
"command": "mysql --batch --raw --column-names --default-character-set=utf8mb4 --host gateway02.us-east-1.prod.aws.tidbcloud.com --port 4000 --user 4CrrYuB5tme73Qo.root --database 7PAT67UmWoxv8vwp8Bbcv6 --execute CREATE TABLE IF NOT EXISTS `attestations` (\n `id` int AUTO_INCREMENT NOT NULL,\n `inscriptionId` int NOT NULL,\n `apprenantId` int NOT NULL,\n `sequenceId` int NOT NULL,\n `s3Key` varchar(500) NOT NULL,\n `pdfUrl` varchar(1000) NOT NULL,\n `dateGeneration` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `emailEnvoye` boolean NOT NULL DEFAULT false,\n `dateEnvoiEmail` timestamp NULL,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n CONSTRAINT `attestations_id` PRIMARY KEY(`id`)\n);\n\nCREATE TABLE IF NOT EXISTS `configAttestation` (\n `id` int AUTO_INCREMENT NOT NULL,\n `logoS3Key` varchar(500),\n `logoUrl` varchar(1000),\n `signatureS3Key` varchar(500),\n `signatureUrl` varchar(1000),\n `nomSignataire` varchar(255),\n `fonctionSignataire` varchar(255),\n `texteAttestation` text,\n `createdAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,\n `updatedAt` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,\n CONSTRAINT `configAttestation_id` PRIMARY KEY(`id`)\n);",
"rows": [],
"messages": [],
"stdout": "",
"stderr": "",
"execution_time_ms": 665
}