]> git.digitality.be Git - pdw25-26/commitdiff
Alignement module alertes avec contrat API + format uniforme { ok, data/error }
authorSteph Ponzo <ponzo.stephane2@gmail.com>
Tue, 24 Feb 2026 14:48:27 +0000 (15:48 +0100)
committerSteph Ponzo <ponzo.stephane2@gmail.com>
Tue, 24 Feb 2026 14:49:53 +0000 (15:49 +0100)
Wallette/server/modules/alerts/adapters/mysql.adapter.js
Wallette/server/modules/alerts/alerts.controller.js
Wallette/server/modules/alerts/alerts.router.js

index 830efbd10a2b36ab7b7dd1acf9f396ba45c59ce4..2f73ec2e441dce49cb738b10cf8bdf12982c93f0 100644 (file)
@@ -149,6 +149,9 @@ export function createMySQLAdapter(dbConnection, options = {}) {
         // =====================================================\r
         async getAlertHistory(userId, limit = 50) {\r
             try {\r
+                // Sécuriser le limit (doit être un entier)\r
+                const safeLimit = parseInt(limit) || 50;\r
+\r
                 const sql = `\r
                     SELECT\r
                         ae.*,\r
@@ -158,10 +161,10 @@ export function createMySQLAdapter(dbConnection, options = {}) {
                     JOIN ${tables.alerts} ar ON ae.rule_id = ar.rule_id\r
                     WHERE ar.user_id = ?\r
                     ORDER BY ae.timestamp_ms DESC\r
-                    LIMIT ?\r
+                    LIMIT ${safeLimit}\r
                 `;\r
 \r
-                const [rows] = await dbConnection.execute(sql, [userId, limit]);\r
+                const [rows] = await dbConnection.execute(sql, [userId]);\r
                 return rows;\r
 \r
             } catch (error) {\r
index a15378779e4e5ac3e9d7af54c3a2f5f1c3407469..5aee41d265a75a355f20b0bab5d4467e7c1e33d8 100644 (file)
 // =========================================================\r
-// ALERTS CONTROLLER - VERSION RÉUTILISABLE\r
+// ALERTS CONTROLLER - CONTRAT API + CRUD\r
 // =========================================================\r
-// RÔLE : Gestion des requêtes et réponses HTTP\r
+// Format de réponse uniforme :\r
+//   Succès : { ok: true, data: { ... } }\r
+//   Erreur : { ok: false, error: { code, message } }\r
 // =========================================================\r
 \r
-\r
 /**\r
- * Crée un controller d'alertes réutilisable\r
- *\r
- * @param {Object} alertsService - Service d'alertes (créé par createAlertsService)\r
- * @param {Object} alertsRepo - Repository d'alertes (pour certaines méthodes)\r
- *\r
+ * Crée un controller d'alertes selon le contrat API\r
+ * @param {Object} alertsService - Service d'alertes\r
+ * @param {Object} alertsRepo - Repository d'alertes\r
  * @returns {Object} Controller avec toutes les méthodes\r
  */\r
 export function createAlertsController(alertsService, alertsRepo) {\r
 \r
     console.log("Controller d'alertes initialisé");\r
 \r
+    // =========================================================\r
+    // HELPERS - Format de réponse uniforme\r
+    // =========================================================\r
+\r
+    const sendSuccess = (res, data, status = 200) => {\r
+        res.status(status).json({ ok: true, data });\r
+    };\r
+\r
+    const sendError = (res, code, message, status = 400) => {\r
+        res.status(status).json({ ok: false, error: { code, message } });\r
+    };\r
+\r
     return {\r
 \r
         // =====================================================\r
-        // POST /api/alerts/process-signal\r
+        // CONTRAT API\r
         // =====================================================\r
+\r
+        // GET /api/alerts?userId=...\r
         /**\r
-         * Endpoint pour traiter un signal crypto\r
-         *\r
-         * Appelé par le module Strategy (Sacha) quand il\r
-         * génère un signal BUY/SELL/HOLD\r
-         *\r
-         * Body attendu :\r
-         * {\r
-         *   "userId": "uuid",\r
-         *   "pairId": 1,\r
-         *   "pair": "BTC/EUR",\r
-         *   "action": "BUY",\r
-         *   "confidence": 0.87,\r
-         *   "criticality": "WARNING",\r
-         *   "reason": "Indicators show...",\r
-         *   "priceAtSignal": 45000.50\r
-         * }\r
+         * Liste les règles d'alerte d'un utilisateur\r
+         * Query params : userId (requis)\r
          */\r
-        async processSignal(req, res) {\r
+        async getAlerts(req, res) {\r
             try {\r
-                // ==================================================================================\r
-                // 1. RÉCUPÉRER LES DONNÉES DU BODY\r
-                // ==================================================================================\r
-                const signal = req.body;\r
+                const { userId } = req.query;\r
 \r
-                // ==================================================================================\r
-                // 2. VALIDATION DES DONNÉES\r
-                // ==================================================================================\r
-                // Vérifier que tous les champs obligatoires sont présents\r
-                const requiredFields = ['userId', 'pairId', 'pair', 'action', 'confidence', 'criticality'];\r
-                /**\r
-                 *  on remplit le tableau des champs manquant ou faux du contenu de signal\r
-                 */\r
-                const missingFields = requiredFields.filter(field => !signal[field]);\r
-                // dans ce cas on retourne une erreur et le noms des champs en cause\r
-                if (missingFields.length > 0) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: 'Champs manquants',\r
-                        missingFields: missingFields\r
-                    });\r
+                if (!userId) {\r
+                    return sendError(res, 'MISSING_USER_ID', 'Le paramètre userId est requis', 400);\r
                 }\r
 \r
-                // Valider le format de action\r
-                const validActions = ['BUY', 'SELL', 'HOLD', 'STOP_LOSS'];\r
-                if (!validActions.includes(signal.action)) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: `Action invalide. Doit être: ${validActions.join(', ')}`\r
-                    });\r
-                }\r
+                const alerts = await alertsRepo.findActiveRulesForSignal(userId, null);\r
 \r
-                // Valider le format de criticality\r
-                const validCriticalities = ['CRITICAL', 'WARNING', 'INFO'];\r
-                if (!validCriticalities.includes(signal.criticality)) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: `Criticality invalide. Doit être: ${validCriticalities.join(', ')}`\r
-                    });\r
-                }\r
+                sendSuccess(res, {\r
+                    alerts,\r
+                    count: alerts.length\r
+                });\r
 \r
-                // Valider la confidence (doit être entre 0 et 1)\r
-                if (signal.confidence < 0 || signal.confidence > 1) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: 'Confidence doit être entre 0 et 1'\r
-                    });\r
+            } catch (error) {\r
+                console.error('Erreur getAlerts:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
+            }\r
+        },\r
+\r
+        // POST /api/alerts\r
+        /**\r
+         * Crée une nouvelle règle d'alerte\r
+         * Body : { userId, pairId, channel, minConfidence, severity, ... }\r
+         */\r
+        async createAlert(req, res) {\r
+            try {\r
+                const ruleData = req.body;\r
+\r
+                // Validation\r
+                if (!ruleData.userId) {\r
+                    return sendError(res, 'MISSING_USER_ID', 'userId est requis', 400);\r
+                }\r
+                if (!ruleData.channel) {\r
+                    return sendError(res, 'MISSING_CHANNEL', 'channel est requis', 400);\r
                 }\r
 \r
-                // =================================================================================\r
-                // 3. APPELER LE SERVICE (injecté)\r
-                // =================================================================================\r
-                // Le service contient toute la logique\r
-                await alertsService.processSignal(signal);\r
+                const newAlert = await alertsRepo.createRule(ruleData);\r
 \r
-                // =================================================================================\r
-                // 4. RÉPONDRE AU CLIENT\r
-                // =================================================================================\r
-                res.status(200).json({\r
-                    success: true,\r
-                    message: 'Signal traité avec succès',\r
-                    signal: {\r
-                        action: signal.action,\r
-                        pair: signal.pair,\r
-                        confidence: signal.confidence\r
-                    }\r
-                });\r
+                sendSuccess(res, { alert: newAlert }, 201);\r
 \r
             } catch (error) {\r
-                // =================================================================================\r
-                // GESTION DES ERREURS\r
-                // =================================================================================\r
-                console.error(' Erreur dans processSignal controller:', error);\r
-\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: 'Erreur serveur lors du traitement du signal',\r
-                    details: process.env.NODE_ENV === 'development' ? error.message : undefined\r
-                });\r
+                console.error('Erreur createAlert:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         },\r
 \r
-        // =====================================================\r
-        // GET /api/alerts/rules/:userId\r
-        // =====================================================\r
+        // POST /api/alerts/:id/toggle\r
         /**\r
-         * Récupère toutes les règles d'alerte d'un utilisateur\r
-         *\r
-         * Utile pour afficher la configuration dans l'interface\r
+         * Active ou désactive une règle d'alerte\r
+         * Params : id (requis)\r
          */\r
-        async getRules(req, res) {\r
+        async toggleAlert(req, res) {\r
             try {\r
-                const { userId } = req.params;\r
+                const { id } = req.params;\r
 \r
-                // Validation\r
-                if (!userId) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: 'userId manquant'\r
-                    });\r
+                // Récupérer la règle actuelle\r
+                const alert = await alertsRepo.findRuleById(id);\r
+\r
+                if (!alert) {\r
+                    return sendError(res, 'ALERT_NOT_FOUND', 'Règle d\'alerte non trouvée', 404);\r
                 }\r
 \r
-                // Récupérer les règles (via repo injecté)\r
-                const rules = await alertsRepo.findActiveRulesForSignal(userId, null);\r
+                // Inverser l'état enabled\r
+                const newEnabledState = !alert.enabled;\r
+                await alertsRepo.updateRule(id, { enabled: newEnabledState });\r
+\r
+                // Récupérer la règle mise à jour\r
+                const updatedAlert = await alertsRepo.findRuleById(id);\r
 \r
-                res.status(200).json({\r
-                    success: true,\r
-                    count: rules.length,\r
-                    rules: rules\r
+                sendSuccess(res, {\r
+                    alert: updatedAlert,\r
+                    enabled: newEnabledState\r
                 });\r
 \r
             } catch (error) {\r
-                console.error('Erreur dans getRules controller:', error);\r
-\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: 'Erreur serveur'\r
-                });\r
+                console.error('Erreur toggleAlert:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         },\r
 \r
-        // =====================================================\r
-        // GET /api/alerts/history/:userId\r
-        // =====================================================\r
+        // GET /api/alerts/events?userId=...&limit=...\r
         /**\r
-         * Récupère l'historique des alertes d'un utilisateur\r
+         * Récupère l'historique des alertes envoyées\r
+         * Query params : userId (requis), limit (optionnel, défaut 50)\r
          */\r
-        async getHistory(req, res) {\r
+        async getEvents(req, res) {\r
             try {\r
-                const { userId } = req.params;\r
-                const limit = parseInt(req.query.limit) || 50;\r
+                const { userId, limit } = req.query;\r
 \r
                 if (!userId) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: 'userId manquant'\r
-                    });\r
+                    return sendError(res, 'MISSING_USER_ID', 'Le paramètre userId est requis', 400);\r
                 }\r
 \r
-                // Récupérer l'historique (via repo injecté)\r
-                const history = await alertsRepo.getAlertHistory(userId, limit);\r
+                const parsedLimit = parseInt(limit) || 50;\r
+                const events = await alertsRepo.getAlertHistory(userId, parsedLimit);\r
 \r
-                res.status(200).json({\r
-                    success: true,\r
-                    count: history.length,\r
-                    history: history\r
+                sendSuccess(res, {\r
+                    events,\r
+                    count: events.length,\r
+                    limit: parsedLimit\r
                 });\r
 \r
             } catch (error) {\r
-                console.error('Erreur dans getHistory controller:', error);\r
-\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: 'Erreur serveur'\r
-                });\r
+                console.error('Erreur getEvents:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         },\r
 \r
-        // =========================================================================================\r
-        //                                         CRUD\r
-        //==========================================================================================\r
-\r
-\r
         // =====================================================\r
-        // POST /api/alerts/rules - CRÉER UNE RÈGLE\r
+        // CRUD COMPLET\r
         // =====================================================\r
+\r
+        // GET /api/alerts/:id\r
         /**\r
-         * Crée une nouvelle règle d'alerte\r
-         * \r
-         * Body attendu :\r
-         * {\r
-         *   "userId": "uuid",\r
-         *   "pairId": 1,\r
-         *   "channel": "EMAIL",\r
-         *   "minConfidence": 0.8,\r
-         *   "severity": "WARNING",\r
-         *   "cooldownMs": 60000\r
-         * }\r
+         * Récupère le détail d'une règle\r
          */\r
-        async createRule(req, res) {\r
+        async getAlertById(req, res) {\r
             try {\r
-                const ruleData = req.body;\r
+                const { id } = req.params;\r
 \r
-                // Validation des champs obligatoires\r
-                if (!ruleData.userId || !ruleData.channel) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: 'Champs manquants (userId, channel requis)'\r
-                    });\r
-                }\r
+                const alert = await alertsRepo.findRuleById(id);\r
 \r
-                // Appeler le repo (qui appelle l'adapter)\r
-                const newRule = await alertsRepo.createRule(ruleData);\r
+                if (!alert) {\r
+                    return sendError(res, 'ALERT_NOT_FOUND', 'Règle d\'alerte non trouvée', 404);\r
+                }\r
 \r
-                res.status(201).json({\r
-                    success: true,\r
-                    rule: newRule,\r
-                    message: 'Règle créée avec succès'\r
-                });\r
+                sendSuccess(res, { alert });\r
 \r
             } catch (error) {\r
-                console.error('❌ Erreur createRule:', error);\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: error.message\r
-                });\r
+                console.error('❌ Erreur getAlertById:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         },\r
 \r
-        // =====================================================\r
-        // PUT /api/alerts/rules/:id - MODIFIER UNE RÈGLE\r
-        // =====================================================\r
+        // PUT /api/alerts/:id\r
         /**\r
-         * Met à jour une règle existante\r
-         * \r
-         * Body possible :\r
-         * {\r
-         *   "minConfidence": 0.9,\r
-         *   "enabled": true,\r
-         *   "channel": "CONSOLE",\r
-         *   "severity": "CRITICAL"\r
-         * }\r
+         * Modifie une règle d'alerte\r
+         * Body : { minConfidence, enabled, channel, severity, ... }\r
          */\r
-        async updateRule(req, res) {\r
+        async updateAlert(req, res) {\r
             try {\r
                 const { id } = req.params;\r
                 const updates = req.body;\r
 \r
-                // Vérifier qu'il y a quelque chose à mettre à jour\r
                 if (Object.keys(updates).length === 0) {\r
-                    return res.status(400).json({\r
-                        success: false,\r
-                        error: 'Aucune donnée à mettre à jour'\r
-                    });\r
+                    return sendError(res, 'NO_DATA', 'Aucune donnée à mettre à jour', 400);\r
                 }\r
 \r
-                // Appeler le repo (qui appelle l'adapter)\r
-                const updated = await alertsRepo.updateRule(id, updates);\r
-\r
-                if (!updated) {\r
-                    return res.status(404).json({\r
-                        success: false,\r
-                        error: 'Règle non trouvée'\r
-                    });\r
+                // Vérifier que la règle existe\r
+                const existingAlert = await alertsRepo.findRuleById(id);\r
+                if (!existingAlert) {\r
+                    return sendError(res, 'ALERT_NOT_FOUND', 'Règle d\'alerte non trouvée', 404);\r
                 }\r
 \r
-                res.json({\r
-                    success: true,\r
-                    message: 'Règle mise à jour'\r
-                });\r
+                await alertsRepo.updateRule(id, updates);\r
+\r
+                // Récupérer la règle mise à jour\r
+                const updatedAlert = await alertsRepo.findRuleById(id);\r
+\r
+                sendSuccess(res, { alert: updatedAlert });\r
 \r
             } catch (error) {\r
-                console.error('❌ Erreur updateRule:', error);\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: error.message\r
-                });\r
+                console.error('Erreur updateAlert:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         },\r
 \r
-        // =====================================================\r
-        // DELETE /api/alerts/rules/:id - SUPPRIMER UNE RÈGLE\r
-        // =====================================================\r
-        async deleteRule(req, res) {\r
+        // DELETE /api/alerts/:id\r
+        /**\r
+         * Supprime une règle d'alerte\r
+         */\r
+        async deleteAlert(req, res) {\r
             try {\r
                 const { id } = req.params;\r
 \r
-                // Appeler le repo (qui appelle l'adapter)\r
                 const deleted = await alertsRepo.deleteRule(id);\r
 \r
                 if (!deleted) {\r
-                    return res.status(404).json({\r
-                        success: false,\r
-                        error: 'Règle non trouvée'\r
-                    });\r
+                    return sendError(res, 'ALERT_NOT_FOUND', 'Règle d\'alerte non trouvée', 404);\r
                 }\r
 \r
-                res.json({\r
-                    success: true,\r
-                    message: 'Règle supprimée'\r
-                });\r
+                sendSuccess(res, { deleted: true, id });\r
 \r
             } catch (error) {\r
-                console.error('❌ Erreur deleteRule:', error);\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: error.message\r
-                });\r
+                console.error('❌ Erreur deleteAlert:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         },\r
 \r
         // =====================================================\r
-        // GET /api/alerts/rules/detail/:id - DÉTAIL D'UNE RÈGLE\r
+        // ROUTE INTERNE (pour module Strategy)\r
         // =====================================================\r
-        async getRuleById(req, res) {\r
+\r
+        // POST /api/alerts/process-signal\r
+        /**\r
+         * Traite un signal du module Strategy\r
+         * Body : { userId, pairId, pair, action, confidence, criticality, reason }\r
+         */\r
+        async processSignal(req, res) {\r
             try {\r
-                const { id } = req.params;\r
+                const signal = req.body;\r
+\r
+                // Validation\r
+                const requiredFields = ['userId', 'pairId', 'pair', 'action', 'confidence', 'criticality'];\r
+                const missingFields = requiredFields.filter(field => signal[field] === undefined);\r
+\r
+                if (missingFields.length > 0) {\r
+                    return sendError(\r
+                        res,\r
+                        'MISSING_FIELDS',\r
+                        `Champs manquants: ${missingFields.join(', ')}`,\r
+                        400\r
+                    );\r
+                }\r
+\r
+                // Valider action\r
+                const validActions = ['BUY', 'SELL', 'HOLD', 'STOP_LOSS'];\r
+                if (!validActions.includes(signal.action)) {\r
+                    return sendError(\r
+                        res,\r
+                        'INVALID_ACTION',\r
+                        `Action invalide. Valeurs acceptées: ${validActions.join(', ')}`,\r
+                        400\r
+                    );\r
+                }\r
 \r
-                const rule = await alertsRepo.findRuleById(id);\r
+                // Valider criticality\r
+                const validCriticalities = ['CRITICAL', 'WARNING', 'INFO'];\r
+                if (!validCriticalities.includes(signal.criticality)) {\r
+                    return sendError(\r
+                        res,\r
+                        'INVALID_CRITICALITY',\r
+                        `Criticality invalide. Valeurs acceptées: ${validCriticalities.join(', ')}`,\r
+                        400\r
+                    );\r
+                }\r
 \r
-                if (!rule) {\r
-                    return res.status(404).json({\r
-                        success: false,\r
-                        error: 'Règle non trouvée'\r
-                    });\r
+                // Valider confidence\r
+                if (signal.confidence < 0 || signal.confidence > 1) {\r
+                    return sendError(\r
+                        res,\r
+                        'INVALID_CONFIDENCE',\r
+                        'Confidence doit être entre 0 et 1',\r
+                        400\r
+                    );\r
                 }\r
 \r
-                res.json({\r
-                    success: true,\r
-                    rule\r
+                // Traiter le signal\r
+                await alertsService.processSignal(signal);\r
+\r
+                sendSuccess(res, {\r
+                    processed: true,\r
+                    signal: {\r
+                        action: signal.action,\r
+                        pair: signal.pair,\r
+                        confidence: signal.confidence\r
+                    }\r
                 });\r
 \r
             } catch (error) {\r
-                console.error('❌ Erreur getRuleById:', error);\r
-                res.status(500).json({\r
-                    success: false,\r
-                    error: error.message\r
-                });\r
+                console.error('Erreur processSignal:', error);\r
+                sendError(res, 'SERVER_ERROR', error.message, 500);\r
             }\r
         }\r
     };\r
 }\r
-\r
index bc7ac7865f1967343e3592d6e2f670acdfab2cba..5ec1af521908ef101ce244cd7dfb0867954d99fa 100644 (file)
@@ -1,80 +1,74 @@
 // =========================================================\r
-// ALERTS ROUTER - VERSION RÉUTILISABLE\r
+// ALERTS ROUTER - CONTRAT API + CRUD\r
 // =========================================================\r
-// RÔLE : Définir les routes (endpoints) de l'API\r
+// Routes du contrat API :\r
+//   GET  /api/alerts?userId=...           → Liste des alertes\r
+//   POST /api/alerts                      → Créer une alerte\r
+//   POST /api/alerts/:id/toggle           → Activer/désactiver\r
+//   GET  /api/alerts/events?userId=...    → Historique\r
+//\r
+// Routes supplémentaires (CRUD complet) :\r
+//   GET    /api/alerts/:id                → Détail d'une alerte\r
+//   PUT    /api/alerts/:id                → Modifier une alerte\r
+//   DELETE /api/alerts/:id                → Supprimer une alerte\r
+//\r
+// Route interne (pour module Strategy) :\r
+//   POST /api/alerts/process-signal       → Traiter un signal\r
 // =========================================================\r
 \r
-\r
 import express from 'express';\r
 \r
-// =========================================================\r
-// FACTORY FUNCTION : Créer un router\r
-// =========================================================\r
 /**\r
- * Crée un router d'alertes réutilisable\r
- *\r
- * @param {Object} alertsController - Controller d'alertes (créé par createAlertsController)\r
- *\r
+ * Crée un router d'alertes selon le contrat API\r
+ * @param {Object} alertsController - Controller d'alertes\r
  * @returns {express.Router} Router Express configuré\r
  */\r
 export function createAlertsRouter(alertsController) {\r
 \r
     const router = express.Router();\r
-\r
     console.log("Router d'alertes initialisé");\r
 \r
-// =========================================================\r
-    // DÉFINITION DES ROUTES\r
-// =========================================================\r
+    // =========================================================\r
+    // CONTRAT API\r
+    // =========================================================\r
+\r
+    // GET /api/alerts?userId=...\r
+    // Liste les règles d'alerte d'un utilisateur\r
+    router.get('/', alertsController.getAlerts);\r
+\r
+    // POST /api/alerts\r
+    // Crée une nouvelle règle d'alerte\r
+    router.post('/', alertsController.createAlert);\r
+\r
+    // GET /api/alerts/events?userId=...&limit=...\r
+    // Récupère l'historique des alertes envoyées\r
+    // ⚠️ DOIT être AVANT /:id sinon "events" sera pris comme un id\r
+    router.get('/events', alertsController.getEvents);\r
+\r
     // POST /api/alerts/process-signal\r
-// =========================================================\r
-    /**\r
-     * Traiter un signal crypto et envoyer les alertes\r
-     *\r
-     * Exemple d'appel :\r
-     * POST http://localhost:3000/api/alerts/process-signal\r
-     * Body : {\r
-     *   "userId": "user-123",\r
-     *   "pairId": 1,\r
-     *   "pair": "BTC/EUR",\r
-     *   "action": "BUY",\r
-     *   "confidence": 0.85,\r
-     *   "criticality": "WARNING",\r
-     *   "reason": "RSI oversold + MACD crossover"\r
-     * }\r
-     */\r
+    // Traite un signal et envoie les notifications\r
+    // ⚠️ DOIT être AVANT /:id/toggle sinon "process-signal" sera pris comme un id\r
     router.post('/process-signal', alertsController.processSignal);\r
 \r
-// =============================================================================\r
-    // GET /api/alerts/rules/:userId\r
-// =============================================================================\r
-    /**\r
-     * Récupérer les règles d'alerte d'un utilisateur\r
-     *\r
-     * Exemple d'appel :\r
-     * GET http://localhost:3000/api/alerts/rules/user-123\r
-     */\r
-    router.get('/rules/:userId', alertsController.getRules);\r
+    // POST /api/alerts/:id/toggle\r
+    // Active ou désactive une règle d'alerte\r
+    router.post('/:id/toggle', alertsController.toggleAlert);\r
 \r
-// =============================================================================\r
-    // GET /api/alerts/history/:userId\r
-// =============================================================================\r
-    /**\r
-    * Récupérer l'historique des alertes d'un utilisateur\r
-     *\r
-     * Exemple d'appel :\r
-     * GET http://localhost:3000/api/alerts/history/user-123?limit=20\r
-     */\r
-    router.get('/history/:userId', alertsController.getHistory);\r
+    // =========================================================\r
+    // CRUD COMPLET (en plus du contrat)\r
+    // =========================================================\r
 \r
-// =============================================================================\r
-//                                  CRUD\r
-// =============================================================================\r
-    router.post('/rules', alertsController.createRule);\r
-    router.get('/rules/detail/:id', alertsController.getRuleById);\r
-    router.put('/rules/:id', alertsController.updateRule);\r
-    router.delete('/rules/:id', alertsController.deleteRule);\r
+    // GET /api/alerts/:id\r
+    // Détail d'une règle\r
+    router.get('/:id', alertsController.getAlertById);\r
+\r
+    // PUT /api/alerts/:id\r
+    // Modifier une règle\r
+    router.put('/:id', alertsController.updateAlert);\r
+\r
+    // DELETE /api/alerts/:id\r
+    // Supprimer une règle\r
+    router.delete('/:id', alertsController.deleteAlert);\r
 \r
     return router;\r
 }\r
-\r