Alert-Parent Backend¶
Description¶
Alert-Parent est une plateforme interne qui permet aux équipes pédagogiques d'EPITECH Bénin d'envoyer des alertes aux parents et référents financiers des étudiants concernant leurs résultats académiques.
- Repository : EpitechAfrik/alert-parent-backend
- URL production : https://api.epitools.bj
- Port :
127.0.0.1:5011(hôte) →5010(conteneur)
Fonctionnalités principales¶
- Gestion des promos : création de promotions par cycle (Bachelor, MSc, Codac) et année de fin
- Gestion multi-campus : création de campus, promos par campus, étudiants rattachés à un campus
- Gestion des étudiants : création unitaire, import CSV en masse, changement de campus/classe/promo
- Alertes pédagogiques : Tepitech, Stumpers, Modules, Présences, Projets, Roadblocks, -42, Évaluation de stage, Absence, Comportement, Information générale
- Templates d'alertes dynamiques : templates globaux, surcharges par campus, corps HTML WYSIWYG, variables et fichiers attendus
- Campagnes d'alertes : preview éphémère, brouillon, simulation d'envoi (dry-run), envoi, historique, surcharges par campagne ou par étudiant
- File d'attente email : les alertes sont mises en queue dans MongoDB et envoyées progressivement via l'API Resend
- Authentification : JWT classique + Microsoft Azure AD (SSO)
Architecture¶
Stack technique¶
| Composant | Technologie |
|---|---|
| Framework | Express.js (Node.js 20) |
| Base de données | MongoDB Atlas |
| Email transactionnel | Resend API |
| Authentification | JWT + Microsoft Azure AD |
| Conteneur | Docker (node:20-alpine, pnpm) |
Routes API¶
| Route | Description |
|---|---|
/api/users |
Inscription, login, gestion des comptes admin |
/api/students |
CRUD étudiants, import CSV, bulk update, soft delete |
/api/promos |
CRUD promos, mapping classe/promo |
/api/alerts |
Lecture seule (historique). Les POST d'envoi par type sont désactivés (HTTP 410) → voir /api/alert-campaigns |
/api/campuses |
CRUD campus |
/api/alert-templates |
Templates d'alertes dynamiques (+ field-options par classe) |
/api/alert-campaigns |
Campagnes d'alertes, preview, brouillons, simulation d'envoi, envoi, mode libre, variables |
/api/docs |
Documentation Swagger interactive |
/health |
Endpoint de vérification de santé |
Modèle de données¶
Student
├── firstname, lastname, email, sexe
├── campus (ObjectId → Campus)
├── promo (ObjectId → Promo, populated: {cycle, end_year, campus})
├── classe (TEK1, TEK1 PSO, TEK2, TEK3, PreMSC, MSC1, MSC2, CODAC)
├── active (Boolean, default: true) — soft delete
├── ref_financier_1_* (prénom, nom, email, sexe)
└── ref_financier_2_* (optionnel)
Campus
├── name, city, country, address
├── active (Boolean)
└── createdAt, updatedAt
Promo
├── campus (ObjectId → Campus)
├── cycle (BACHELOR, MSC, CODAC)
└── end_year (ex: 2027)
AlertTemplate
├── key, name, topic, campus (optionnel), baseTemplate (optionnel)
├── ruleKey (règle métier: tepitech, module, project, stumper)
├── subjectTemplate, bodyTemplate (HTML WYSIWYG)
├── variables, variablesSchema, fileSchemas
├── source (system/user), active
└── createdBy, updatedBy
AlertCampaign
├── name, kind (template | free), topic, campus, promo, classe
├── template (null en mode free)
├── freeSubject, freeBody, extraRecipients (mode free)
├── students, globalVariables, files, rows
├── overrideSubjectTemplate, overrideBodyTemplate, overrideVariables
├── studentOverrides, renderedPreviews, fileWarnings, fileErrors
├── mails, status (draft, previewed, queued, sent, partial_failed)
└── createdBy, sentBy, sentAt
MailSended
├── from (user qui a envoyé), to (email destinataire), cc (optionnel)
├── student (ObjectId → Student, null pour un destinataire libre), campus, template, campaign
├── topic (Tepitech, Module, Présence, etc.)
├── obj (sujet), message (HTML)
├── outgoing (adresse d'expédition affichée, ex: "EPITECH Info <no-reply@epitech.bj>")
├── isSent (yes/no)
└── createdAt, updatedAt
Routes étudiants (détail)¶
| Action | Méthode | Route | Body |
|---|---|---|---|
| Lister (actifs par défaut) | GET | /api/students?active=true |
— |
| Lister les inactifs | GET | /api/students?active=false |
— |
| Créer | POST | /api/students |
{firstname, lastname, email, ...} |
| Modifier | PUT | /api/students/:id |
{firstname, lastname, ...} |
| Import CSV | POST | /api/students/import |
FormData |
| Bulk update (classe/promo) | PUT | /api/students/bulk |
{students: [ids], classe, promo} |
| Désactiver | PUT | /api/students/:id/deactivate |
— |
| Réactiver | PUT | /api/students/:id/reactivate |
— |
| Désactiver en masse | POST | /api/students/bulk/deactivate |
{students: [ids]} |
Soft delete
Les étudiants ne sont jamais supprimés définitivement. La route DELETE /api/students/:id n'existe plus.
La désactivation conserve l'historique des alertes associées.
Par défaut, GET /api/students ne retourne que les étudiants actifs (active=true).
Promo peuplée
Le champ promo est désormais peuplé automatiquement par l'API. Au lieu d'un simple ObjectId, il retourne {cycle: "BACHELOR", end_year: 2030}.
Campus, templates et campagnes d'alertes¶
Campus¶
Les étudiants et promos sont rattachés à un campus. Les anciennes données sans campus sont rattachées automatiquement au campus par défaut Cotonou au démarrage.
| Action | Méthode | Route |
|---|---|---|
| Lister | GET | /api/campuses |
| Créer | POST | /api/campuses |
| Modifier | PUT | /api/campuses/:id |
| Supprimer | DELETE | /api/campuses/:id |
Une promo est unique par couple campus + cycle + end_year. Deux campus peuvent donc avoir la même promo en parallèle.
Templates dynamiques¶
Les templates ne sont plus figés dans le code. Ils sont stockés en base et peuvent être globaux ou spécifiques à un campus.
| Action | Méthode | Route |
|---|---|---|
| Lister | GET | /api/alert-templates |
| Résoudre le template effectif | GET | /api/alert-templates/resolve?topic=Module&campus=ID |
| Options de champ par classe (selects bornés) | GET | /api/alert-templates/:id/field-options?classe=TEK2 |
| Créer | POST | /api/alert-templates |
| Modifier | PUT | /api/alert-templates/:id |
| Désactiver | DELETE | /api/alert-templates/:id |
Champs importants :
subjectTemplate: sujet texte avec variables{{...}}bodyTemplate: HTML WYSIWYGvariablesSchema: champs à afficher côté frontendfileSchemas: fichiers attendus pour les campagnescampus: vide pour template global, renseigné pour surcharge campus
Variables système générées par le backend :
Les variables computed.* sont calculées au rendu, par exemple computed.earnedCredits, computed.moduleResultSummary, computed.projectResultSummary.
Campagnes d'alertes¶
Le workflow recommandé est :
- Preview éphémère sans sauvegarde.
- Sauvegarde brouillon si l'utilisateur clique sur
Save draft. - Simulation d'envoi (dry-run) optionnelle pour vérifier le nombre de mails générés sans rien envoyer.
- Envoi en deux appels
create/update -> send. - Historique uniquement pour les campagnes envoyées ou en file d'attente.
| Action | Méthode | Route |
|---|---|---|
| Preview sans persistance | POST | /api/alert-campaigns/preview-draft |
| Créer un brouillon | POST | /api/alert-campaigns |
| Lister les brouillons | GET | /api/alert-campaigns?scope=drafts |
| Modifier un brouillon | PUT | /api/alert-campaigns/:id |
| Supprimer un brouillon | DELETE | /api/alert-campaigns/:id |
| Preview d'un brouillon sauvegardé | POST | /api/alert-campaigns/:id/preview |
| Simuler l'envoi (dry-run, aucun mail réel) | POST | /api/alert-campaigns/:id/simulate-send |
| Envoyer | POST | /api/alert-campaigns/:id/send |
Historique (paginé, filtres campus/promo/classe/status) |
GET | /api/alert-campaigns?scope=history |
DELETE /api/alert-campaigns/:id est autorisé uniquement pour les brouillons draft ou anciennes previews previewed sans mail envoyé/queue.
send crée un MailSended par parent renseigné. Les deux parents reçoivent l'alerte si les deux emails existent.
Simulation d'envoi (simulate-send)
Ajoutée le plus récemment (feat(alert-campaigns): add send simulation). Rejoue exactement la même
résolution de destinataires et le même rendu que send (mêmes validations, mêmes fichiers, même
ciblage), mais n'insère aucun MailSended et ne touche pas au statut de la campagne. La réponse
(controllers/alertCampaignController.js → simulateSendAlertCampaign) renvoie
deliveryMode: "dry_run", canSend, recipientsCount, simulatedMailsCount, realEmailsSent: 0,
les warnings/errors de validation, et un sample des 5 premiers mails qui seraient générés
(destinataire + sujet). Utile pour vérifier le volume et le contenu avant un envoi réel à un
grand groupe.
Historique — payload allégé
Depuis fix(alert-campaigns): lighten history list payload, GET /api/alert-campaigns ne renvoie
plus les documents complets (fichiers, previews rendues, etc.) mais un résumé par campagne :
targetSummary (libellés promo/classe, avec repli sur les promos/classes des étudiants ciblés
quand le ciblage s'est fait par sélection directe d'étudiants), queuedMails (total), et le détail
queuedMailCount / sentMailsCount / failedMailsCount calculé par agrégation sur MailSended.
failedMailsCount reste toujours à 0 en pratique : MailSended.isSent n'a que deux valeurs
("yes"/"no"), il n'existe pas d'état "failed" distinct côté modèle.
Routes legacy /api/alerts/* désactivées (HTTP 410)
Depuis la refonte de juin 2026, les anciennes routes d'envoi par type
(POST /api/alerts/tepitech, /stumpers, /modules, /roadblocks, /cheaters,
/eval-internship, /presences, /projects, /resend/:id) renvoient 410 Gone.
Tout passe désormais par /api/alert-campaigns. Seules les lectures
GET /api/alerts et GET /api/alerts/:id restent actives.
Moteur de règles métier et validation¶
La logique métier propre à chaque type d'alerte est centralisée dans un registre de règles
(utils/rules/), keyé par le ruleKey du template (fallback sur le topic) :
| ruleKey | Règles |
|---|---|
tepitech |
seuils par classe (TEK1/PSO 600, TEK2 700, TEK3 750) ; sessions par classe (TEK1/PSO 6, TEK2/TEK3 4) ; pass/fail |
stumper |
sessions par topic (solo 10, duo 7) ; seuil de validation 15 ; pass/fail |
module |
grade, crédits acquis, résumés de validation |
project |
grade prévisionnel, trajectoire de validation |
Un validateur unifié (campaignValidator.js) est appliqué au preview et à l'envoi.
Il renvoie { errors[], warnings[] } et couvre 3 couches :
- Champs requis déclarés dans
variablesSchema(lerequiredest désormais réellement appliqué). - Fichiers : colonnes, login, croisements (
campaignFiles.js). - Règle métier : ex. session hors plage pour la classe → erreur
invalid_session.
S'il y a des errors, la requête renvoie 400 et l'envoi est bloqué.
Options de champ dynamiques¶
GET /api/alert-templates/:id/field-options?classe=<classe> renvoie le variablesSchema du
template avec les options résolues selon la classe et le topic — pour que le frontend
affiche un <select> borné aux valeurs valides (sessions Tepitech 6 en TEK1 / 4 en TEK2,
solo 10, duo 7) plutôt qu'une saisie libre rejetée ensuite.
Mode libre (composer sans template)¶
Une campagne porte un champ kind :
template(défaut) : construite depuis unAlertTemplate.free: composer ad hoc —freeSubject+freeBody(WYSIWYG) avec variables{{...}}, destinataires = étudiants ciblés et/ouextraRecipients(emails libres).
| Action | Méthode | Route |
|---|---|---|
| Catalogue des variables DB + snippets (salutation/signature) | GET | /api/alert-campaigns/variables |
| Enregistrer une campagne libre comme template | POST | /api/alert-campaigns/:id/save-as-template |
En mode libre, template vaut null et les MailSended des destinataires libres ont student: null.
Civilité et rendu¶
- Le destinataire est adressé par civilité :
recipient.salutation= « Mr <Nom> » / « Mme <Nom> » (repli « Madame, Monsieur »). - Les mails sont rendus à la charte EPITECH Africa : carte blanche élargie à
max-width:1024px, logo EPITECH dans le header, eyebrow + titre en majuscules (police Anton), accent couleur par topic (utils/mailPresentation.js), footer terminal. Le HTML partagé vit dansmailBody()(utils/mailBodies.js), importé aussi bien par le chemin legacy (mailSend.js) que par le moteur de rendu des campagnes (templateRenderer.js) — un seul header/footer pour tous les mails. - Logo en pièce jointe inline (CID), pas en image externe : le header référence
<img src="cid:epitechlogo">(utils/mailBodies.js), et le PNG est attaché à chaque envoi Resend viaresendLogoAttachment()(utils/mailLogo.js, logo encodé en base64 dans le code). Avant ce changement (feat(mail): logo EPITECH en inline (CID) au lieu d'une image externe), le logo était chargé depuis une URL externe — bloqué par défaut par la plupart des clients mail ("afficher les images") et cassé si l'URL devenait inaccessible. L'inline CID s'affiche sans action de l'utilisateur.
File d'attente email (Resend)¶
Un processus asynchrone tourne en boucle dans server.js :
- Toutes les 5 secondes, il cherche un
MailSendedavecisSent: "no" - Il l'envoie via l'API Resend, avec le logo (
resendLogoAttachment()) joint en pièce inline CID sur chaque envoi - En cas de succès, il marque
isSent: "yes"— et si c'était le dernier mail en attente de sa campagne (campaignrenseigné,MailSended.countDocuments({campaign, isSent:"no"}) === 0), la campagne bascule au statutsent - En cas d'erreur, il attend 60 secondes avant de réessayer
Ce mécanisme découple la création d'alertes de leur envoi effectif.
Le statut partial_failed existe mais n'est jamais posé automatiquement
alertCampaignStatus définit bien partial_failed (utilisé côté filtre d'historique et pour verrouiller
l'édition d'une campagne déjà envoyée), mais rien dans la boucle mail de server.js ne fait actuellement
transitionner une campagne vers cet état — un échec Resend individuel marque juste ce MailSended
en retry, sans jamais marquer la campagne "partiellement échouée". À vérifier/adapter si ce statut doit
un jour refléter un échec réel.
Collections MongoDB¶
| Collection | Rôle |
|---|---|
students |
Étudiants avec leurs référents financiers |
promos |
Promotions par cycle et année |
mailsendeds |
File d'attente des mails à envoyer |
users |
Comptes administrateurs |
emailconfirms |
Tokens de confirmation email |
passwordresets |
Tokens de reset mot de passe |
Diagrammes¶
Architecture globale¶
graph TB
User[Utilisateur / Navigateur]
Frontend["Frontend<br/>alert.epitools.bj"]
Nginx["Nginx<br/>Reverse proxy + TLS"]
Backend["Backend API<br/>Express.js :5010"]
MongoDB["MongoDB Atlas<br/>(Cloud)"]
Resend["Resend API<br/>(Email)"]
AzureAD["Azure AD<br/>(SSO)"]
User --> Nginx
Nginx --> Frontend
Nginx --> Backend
Backend --> MongoDB
Backend --> Resend
Backend --> AzureAD
Flux d'envoi d'alerte¶
sequenceDiagram
participant Admin as Admin (Frontend)
participant API as Backend API
participant DB as MongoDB Atlas
participant Queue as Mail Queue (loop)
participant Resend as Resend API
participant Parent as Parent (Email)
Admin->>API: POST /api/alert-campaigns (brouillon)
Admin->>API: POST /api/alert-campaigns/:id/preview
API->>API: validateCampaign (champs, fichiers, règles)
API-->>Admin: {previews, warnings, errors}
Admin->>API: POST /api/alert-campaigns/:id/send
API->>DB: MailSended.insertMany({isSent: "no"})
API-->>Admin: {campaign (queued), queuedMails}
loop Toutes les 5 secondes
Queue->>DB: MailSended.find({isSent: "no"})
Queue->>Resend: resend.emails.send(...)
Resend->>Parent: Email envoyé
Queue->>DB: MailSended.update({isSent: "yes"})
end
Docker¶
Dockerfile¶
L'image utilise node:20-alpine avec pnpm (installé via npm install -g pnpm@10.18.3) comme gestionnaire de paquets. L'application tourne en tant qu'utilisateur non-root (nodejs, uid 1001) pour la sécurité.
Cache BuildKit pour pnpm install (ea35800e)
RUN --mount=type=cache,id=pnpm-store,target=/pnpm/store pnpm install --frozen-lockfile --prod --store-dir /pnpm/store
persiste le store pnpm entre les builds CI (GitHub Actions cache via cache-from/cache-to: type=gha
dans deploy.yml) pour accélérer significativement le build de l'image sur le runner self-hosted.
docker-compose.yml¶
services:
backend:
image: ghcr.io/epitechafrik/alert-parent-backend:${IMAGE_TAG:-latest}
env_file: .env
ports:
- "127.0.0.1:5011:5010"
restart: always
healthcheck:
test: ["CMD", "curl", "-sf", "http://localhost:5010/health"]
interval: 30s
timeout: 10s
retries: 3
start_period: 15s
Points importants :
- L'image est tirée depuis GHCR (
ghcr.io/epitechafrik/alert-parent-backend) - Le port est bindé sur
127.0.0.1uniquement (accessible seulement via Nginx) - Les variables d'environnement sont chargées depuis
.envviaenv_file - Le healthcheck vérifie
/healthsur le port interne5010
CI/CD¶
Pipeline .github/workflows/deploy.yml, déclenché sur push vers main, 3 jobs :
graph LR
Build["build<br/>Docker → GHCR"] --> Deploy["deploy<br/>SCP compose + SSH pull/up"] --> Notify["notify<br/>webhook succès/échec"]
- build : build l'image (
context: ./backend), tagsha-<courtsha>+latest, push surghcr.io/epitechafrik/alert-parent-backend. - deploy : SCP du
docker-compose.ymlvers le VPS, SSH pour reconstruire.envdepuis le secretDOTENV_FILE,docker login ghcr.io,IMAGE_TAG=sha-<sha> docker compose pull backendpuisup -d --no-deps backend(rolling update, pas d'arrêt du service avant le pull), poll de/health(12 tentatives × 5s), puisdocker image prune -f --filter "until=168h". - notify : POST vers
secrets.WEBHOOK_URL(succès ou échec) — no-op silencieux si le secret n'est pas configuré.
Jobs sur runner self-hosted, pas ubuntu-latest
Les 3 jobs tournent sur un runner GitHub Actions self-hosted (probablement le VPS lui-même ou une
machine dédiée à proximité — le SSH vers le VPS dans le job deploy suggère qu'ils ne sont pas
identiques, mais ce n'est pas confirmé). Si ce runner est arrêté ou déconnecté, aucun déploiement
ne part, même si le code est correct — GitHub Actions n'affiche alors qu'un job qui reste "queued"
indéfiniment. Voir la continuité des accès externes pour qui peut administrer ce runner.
Secrets GitHub Actions utilisés :
| Secret | Rôle |
|---|---|
SERVER_HOST |
Adresse du VPS (SCP + SSH) |
SERVER_USERNAME |
Utilisateur SSH |
SERVER_SSH_KEY |
Clé privée SSH |
DOTENV_FILE |
Contenu complet du .env de production, réécrit à chaque déploiement |
WEBHOOK_URL |
Webhook de notification succès/échec (optionnel) |
GITHUB_TOKEN |
Fourni automatiquement par GitHub Actions, utilisé pour push/pull GHCR |
Health Endpoint¶
GET /health¶
Utilisé par Docker Compose (healthcheck), Uptime Kuma (monitoring) et le pipeline CI/CD (vérification post-déploiement).
Réponse (200 OK)¶
{
"status": "ok",
"timestamp": "2026-02-22T10:30:00.000Z",
"uptime_seconds": 86400,
"environment": "production",
"dependencies": {
"mongodb": {
"status": "ok",
"latency_ms": 12
},
"resend": {
"status": "configured"
},
"smtp": {
"status": "configured"
}
}
}
Réponse (503 Service Unavailable)¶
Retourné quand MongoDB est inaccessible :
{
"status": "degraded",
"timestamp": "2026-02-22T10:30:00.000Z",
"uptime_seconds": 120,
"environment": "production",
"dependencies": {
"mongodb": {
"status": "error",
"latency_ms": null
},
"resend": {
"status": "configured"
},
"smtp": {
"status": "configured"
}
}
}
Utilisation¶
# Test simple
curl http://127.0.0.1:5011/health
# Test avec formatage
curl -s http://127.0.0.1:5011/health | python3 -m json.tool
# Vérifier uniquement le status code
curl -o /dev/null -s -w "%{http_code}" http://127.0.0.1:5011/health
# 200 = OK, 503 = dégradé
Variables d'environnement¶
Toutes les variables sont définies dans le fichier .env à la racine du projet (jamais commité dans git). Le template est dans .env.example.
Application¶
| Variable | Description | Exemple |
|---|---|---|
NODE_ENV |
Mode d'exécution | production |
PORT |
Port interne du serveur | 5010 |
Base de données¶
| Variable | Description | Exemple |
|---|---|---|
MONGO_URI |
URI MongoDB Atlas | mongodb+srv://user:pass@cluster.mongodb.net/dbname |
Authentification¶
| Variable | Description | Exemple |
|---|---|---|
JWT_SECRET |
Clé secrète JWT (64 chars min) | chaîne aléatoire longue |
ADMIN_EMAIL |
Email du premier admin créé au démarrage | admin@epitech.eu |
PASSWORD_SIZE |
Taille minimum des mots de passe | 8 |
PASSWORD_CAPITAL |
Exiger une majuscule | true |
PASSWORD_LOWERCASE |
Exiger une minuscule | true |
PASSWORD_NUMBER |
Exiger un chiffre | true |
PASSWORD_CHAR |
Exiger un caractère spécial | true |
Email¶
| Variable | Description | Exemple |
|---|---|---|
MAIL_NAME |
Nom d'expéditeur | EPITECH Info |
MAIL_HOST |
Serveur SMTP | alouette.o2switch.net |
MAIL_PORT |
Port SMTP | 465 |
MAIL_ADDR |
Adresse d'envoi | no-reply@epitech.bj |
MAIL_PASS |
Mot de passe SMTP | secret |
RESEND_TOKEN |
Token API Resend (envoi des alertes de campagne + boucle mail) | re_xxxxx |
Variables MAIL_CC_* (Career Center)
sample.env et utils/mailSend.js définissent aussi un second compte SMTP dédié
(MAIL_CC_NAME, MAIL_CC_HOST, MAIL_CC_PORT, MAIL_CC_ADDR, MAIL_CC_PASS) pour les mails
envoyés côté Career Center. Il ne fait pas partie du chemin d'envoi des campagnes d'alertes
(Resend), c'est un transport SMTP séparé pour les mails legacy (confirmation de compte,
reset mot de passe, notation de stage).
URLs¶
| Variable | Description | Exemple |
|---|---|---|
CORS_ORIGIN |
Origine autorisée CORS | https://alert.epitools.bj |
FRONT_URL |
URL du frontend | https://alert.epitools.bj |
API_URL |
URL publique de l'API | https://api.epitools.bj |
Microsoft Azure AD (optionnel)¶
| Variable | Description |
|---|---|
MICROSOFT_CLIENT_ID |
Client ID de l'App Registration Azure |
MICROSOFT_TENANT_ID |
Tenant ID Azure |
Docker¶
| Variable | Description | Exemple |
|---|---|---|
IMAGE_TAG |
Tag de l'image Docker à utiliser | latest ou sha-abc1234 |
Rollback¶
Quand faire un rollback¶
- Le déploiement a cassé une fonctionnalité critique
- Le health check échoue après un déploiement
- Une régression est détectée en production
Procédure¶
# 1. Trouver la version précédente
docker images ghcr.io/epitechafrik/alert-parent-backend --format "table {{.Tag}}\t{{.CreatedAt}}"
# 2. Exécuter le rollback
cd /root/projects/alert-parent/alert-parent-backend
./scripts/rollback.sh sha-XXXXXXX
# 3. Vérifier
curl -s http://127.0.0.1:5011/health | python3 -m json.tool
docker compose logs backend --tail 20
Après le rollback¶
- Investiguer la cause du problème sur la branche
main - Corriger et pousser un fix
- Le pipeline redéploiera automatiquement
Backups¶
MongoDB Atlas¶
La base de données est hébergée sur MongoDB Atlas, qui gère automatiquement :
- Snapshots automatiques : toutes les 6 heures
- Rétention : configurable dans Atlas (7 jours par défaut)
- Point-in-time recovery : restauration à n'importe quel moment
Backup manuel¶
mongodump --uri="mongodb+srv://USER:PASSWORD@cluster.mongodb.net/al3rtParent" --out=./dump-$(date +%Y%m%d)