From c1c9b040f05454f461bccfdac9db88af703505d0 Mon Sep 17 00:00:00 2001 From: Sacheat Date: Sun, 1 Mar 2026 15:03:18 +0100 Subject: [PATCH] =?utf8?q?Ajout=20des=20routes=20pour=20la=20strat=C3=A9gi?= =?utf8?q?e?= MIME-Version: 1.0 Content-Type: text/plain; charset=utf8 Content-Transfer-Encoding: 8bit --- .../strategy/repositories/SignalRepo.js | 30 ++++++ .../strategy/repositories/StrategyRepo.js | 33 +++++++ Wallette/server/modules/strategy/server.js | 97 +++++++++++++------ 3 files changed, 132 insertions(+), 28 deletions(-) diff --git a/Wallette/server/modules/strategy/repositories/SignalRepo.js b/Wallette/server/modules/strategy/repositories/SignalRepo.js index b9adfd5..1956a1a 100644 --- a/Wallette/server/modules/strategy/repositories/SignalRepo.js +++ b/Wallette/server/modules/strategy/repositories/SignalRepo.js @@ -43,6 +43,36 @@ class SignalRepo { throw error; } } + + /** + * Récupère le tout dernier signal généré pour un utilisateur et une paire spécifique. + * Appelée par le Dashboard Web (GET /api/signal/current) + */ + async getLatestSignal(userId, pairCode) { + const sql = ` + SELECT + s.signal_id, + s.action, + s.confidence, + s.reason, + s.price_at_signal, + s.optimal_price, + s.stop_loss_price, + s.timestamp_ms, + us.mode, + p.pair_code + FROM signals s + JOIN user_strategies us ON s.user_strategy_id = us.user_strategy_id + JOIN pairs p ON us.pair_id = p.pair_id + WHERE us.user_id = ? AND p.pair_code = ? + ORDER BY s.timestamp_ms DESC + LIMIT 1 + `; + + const [rows] = await pool.query(sql, [userId, pairCode]); + + return rows.length > 0 ? rows[0] : null; + } } const signalRepo = new SignalRepo(); diff --git a/Wallette/server/modules/strategy/repositories/StrategyRepo.js b/Wallette/server/modules/strategy/repositories/StrategyRepo.js index b0cdc80..3432d54 100644 --- a/Wallette/server/modules/strategy/repositories/StrategyRepo.js +++ b/Wallette/server/modules/strategy/repositories/StrategyRepo.js @@ -30,6 +30,39 @@ class StrategyRepo { throw error; } } + + /** + * Crée ou met à jour la stratégie d'un utilisateur pour une paire donnée. + * Appelée par le Frontend / App Mobile (POST /api/strategy/select) + */ + async upsertUserStrategy(userId, pairId, mode, params) { + const now = Date.now(); + const strategyKey = `${mode}_${pairId}`; // Génération d'une clé simple + + // On vérifie si l'utilisateur a déjà une configuration pour cette paire + const checkSql = `SELECT user_strategy_id FROM user_strategies WHERE user_id = ? AND pair_id = ?`; + const [rows] = await db.query(checkSql, [userId, pairId]); // ⚠️ Remplace 'db' par le nom de ta variable de connexion (souvent 'pool' ou 'db') + + if (rows.length > 0) { + // MISE À JOUR : La stratégie existe, on l'écrase avec le nouveau mode + const updateSql = ` + UPDATE user_strategies + SET mode = ?, params = ?, strategy_key = ?, is_active = 1, updated_at_ms = ? + WHERE user_id = ? AND pair_id = ? + `; + await db.query(updateSql, [mode, params, strategyKey, now, userId, pairId]); + return { action: 'updated', user_id: userId, pair_id: pairId, mode }; + } else { + // CRÉATION : C'est la première fois qu'il configure cette paire + const insertSql = ` + INSERT INTO user_strategies + (user_strategy_id, user_id, pair_id, strategy_key, mode, params, is_active, created_at_ms, updated_at_ms) + VALUES (UUID(), ?, ?, ?, ?, ?, 1, ?, ?) + `; + await db.query(insertSql, [userId, pairId, strategyKey, mode, params, now, now]); + return { action: 'created', user_id: userId, pair_id: pairId, mode }; + } + } } const strategyRepo = new StrategyRepo(); diff --git a/Wallette/server/modules/strategy/server.js b/Wallette/server/modules/strategy/server.js index 7e6d8c7..251341b 100644 --- a/Wallette/server/modules/strategy/server.js +++ b/Wallette/server/modules/strategy/server.js @@ -1,16 +1,5 @@ // ========================================================= -// STRATEGY SERVICE - Serveur standalone (stub) -// ========================================================= -// Ce fichier est volontairement minimal. -// Son rôle : avoir un process indépendant sur le port 3002 -// pour que le gateway ne retourne pas 502 sur /api/strategy/* -// et pour que le health check soit vert au démarrage. -// -// USAGE : node modules/strategy/server.js (depuis Wallette/server/) -// PORT : process.env.STRATEGY_PORT || 3002 -// -// QUAND Sacha aura finalisé son module, ce fichier sera -// remplacé par un vrai serveur avec les routes strategy. +// STRATEGY SERVICE - Serveur // ========================================================= import cors from 'cors'; @@ -19,6 +8,10 @@ import express from 'express'; import path from 'path'; import { fileURLToPath } from 'url'; +// IMPORT DES REPOSITORIES +import strategyRepo from './repositories/StrategyRepo.js'; +import signalRepo from './repositories/SignalRepo.js'; + const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); dotenv.config({ path: path.resolve(__dirname, '../../.env') }); @@ -30,38 +23,85 @@ app.use(cors()); app.use(express.json()); // ========================================================= -// HEALTH CHECK (requis par le gateway) +// HEALTH CHECk // ========================================================= app.get('/health', (req, res) => { res.json({ ok: true, service: 'strategy-service', port: PORT, - status: 'stub — module Sacha en cours d\'intégration', + status: 'ONLINE — Module opérationnel', }); }); // ========================================================= -// STUB ROUTES /api/strategy/* +// ROUTE 1 : SÉLECTIONNER UNE STRATÉGIE (App Mobile / Web) +// POST /api/strategy/select +// ========================================================= +app.post('/api/strategy/select', async (req, res) => { + try { + const { userId, pairId, mode, params } = req.body; + + if (!userId || !pairId || !mode) { + return res.status(400).json({ ok: false, message: "Données incomplètes (userId, pairId, mode requis)." }); + } + + // Appel au Repository pour insérer/mettre à jour la stratégie + const result = await strategyRepo.upsertUserStrategy(userId, pairId, mode, params || '{}'); + + return res.status(200).json({ + ok: true, + message: "Stratégie configurée avec succès", + data: result + }); + } catch (error) { + console.error("Erreur POST /api/strategy/select :", error); + return res.status(500).json({ ok: false, message: "Erreur interne du serveur." }); + } +}); + +// ========================================================= +// ROUTE 2 : RÉCUPÉRER LE DERNIER SIGNAL (Dashboard Web) +// GET /api/signal/current?userId=...&pair=... +// ========================================================= +app.get('/api/signal/current', async (req, res) => { + try { + const { userId, pair } = req.query; + + if (!userId || !pair) { + return res.status(400).json({ ok: false, message: "Les paramètres userId et pair sont requis." }); + } + + // Appel au Repository pour chercher le dernier signal + const latestSignal = await signalRepo.getLatestSignal(userId, pair); + + if (!latestSignal) { + return res.status(404).json({ ok: true, data: null, message: "Aucun signal récent trouvé." }); + } + + return res.status(200).json({ ok: true, data: latestSignal }); + } catch (error) { + console.error("Erreur GET /api/signal/current :", error); + return res.status(500).json({ ok: false, message: "Erreur interne du serveur." }); + } +}); + +// ========================================================= +// FALLBACK POUR LES AUTRES ROUTES STRATEGY (Toujours en 501) // ========================================================= -// Répond 501 (Not Implemented) pour toutes les routes -// au lieu de laisser le gateway retourner 502. app.use('/api/strategy', (req, res) => { res.status(501).json({ - ok: false, - error: { - code: 'NOT_IMPLEMENTED', - message: 'strategy-service non encore disponible', - }, + ok: false, + error: { code: 'NOT_IMPLEMENTED', message: 'Cette fonctionnalité arrive bientôt.' }, }); }); // ========================================================= -// 404 +// 404 GLOBAL // ========================================================= app.use((req, res) => { res.status(404).json({ - ok: false, + ok: false, error: { code: 'NOT_FOUND', message: 'Route non trouvée' }, }); }); @@ -72,9 +112,10 @@ app.use((req, res) => { app.listen(PORT, () => { console.log(` ╔══════════════════════════════════════════╗ -║ STRATEGY SERVICE (STUB) ║ +║ STRATEGY SERVICE (ONLINE) ║ ║ HTTP : http://localhost:${PORT} ║ -║ → /health = OK ║ -║ → /api/strategy/* = 501 ║ +║ → /health = OK ║ +║ → POST /api/strategy/select = OK ║ +║ → GET /api/signal/current = OK ║ ╚══════════════════════════════════════════╝`); -}); +}); \ No newline at end of file -- 2.50.1