From: Steph Ponzo Date: Fri, 27 Feb 2026 19:51:44 +0000 (+0100) Subject: fix: gateway WS proxy, price PORT env, strategy stub, scripts ps1 X-Git-Url: https://git.digitality.be/?a=commitdiff_plain;h=7db30fbaa57119724517fe55fe69bd08c6d65af6;p=pdw25-26 fix: gateway WS proxy, price PORT env, strategy stub, scripts ps1 --- diff --git a/Wallette/gateway/gateway.js b/Wallette/gateway/gateway.js index 239035d..9cfa0a8 100644 --- a/Wallette/gateway/gateway.js +++ b/Wallette/gateway/gateway.js @@ -8,20 +8,24 @@ // // ROUTING TABLE : // /api/price/* → PRICE_BASE_URL (défaut: http://127.0.0.1:3001) +// /api/pairs → PRICE_BASE_URL (exposition directe des paires) // /api/alerts/* → ALERTS_BASE_URL (défaut: http://127.0.0.1:3003) +// /api/wallets/* → WALLET_BASE_URL (défaut: http://127.0.0.1:3004) // /api/strategy/* → STRATEGY_BASE_URL (défaut: http://127.0.0.1:3002) +// /socket.io/* → ALERTS_BASE_URL (WebSocket proxy) // // PATH REWRITE : -// Les services exposent leurs routes avec le préfixe complet /api/* -// Le gateway transmet donc le path COMPLET tel quel. +// Le path est transmis COMPLET tel quel aux services. // Ex: GET /api/price/current → http://127.0.0.1:3001/api/price/current // ========================================================= import dotenv from 'dotenv'; -import path from 'path'; -import { fileURLToPath } from 'url'; import http from 'http'; import https from 'https'; +import net from 'net'; +import tls from 'tls'; +import path from 'path'; +import { fileURLToPath } from 'url'; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); @@ -31,191 +35,284 @@ dotenv.config({ path: path.resolve(__dirname, '../server/.env') }); // CONFIGURATION // ========================================================= -const GATEWAY_PORT = process.env.GATEWAY_PORT || process.env.PORT || 3000; +const GATEWAY_PORT = Number(process.env.PORT || process.env.GATEWAY_PORT) || 3000; -const UPSTREAMS = { - '/api/price': process.env.PRICE_BASE_URL || 'http://127.0.0.1:3001', - '/api/alerts': process.env.ALERTS_BASE_URL || 'http://127.0.0.1:3003', - '/api/strategy': process.env.STRATEGY_BASE_URL || 'http://127.0.0.1:3002', +const SERVICE_BASE_URLS = { + price: process.env.PRICE_BASE_URL || 'http://127.0.0.1:3001', + alerts: process.env.ALERTS_BASE_URL || 'http://127.0.0.1:3003', + strategy: process.env.STRATEGY_BASE_URL || 'http://127.0.0.1:3002', + wallet: process.env.WALLET_BASE_URL || 'http://127.0.0.1:3004', }; +// Tri par longueur décroissante pour que les préfixes longs matchent en premier +const ROUTES = [ + // WebSocket Socket.IO — proxy TCP direct vers alerts + { prefix: '/socket.io', upstream: SERVICE_BASE_URLS.alerts, name: 'alerts-service', ws: true }, + + // API REST + { prefix: '/api/alerts', upstream: SERVICE_BASE_URLS.alerts, name: 'alerts-service' }, + { prefix: '/api/wallets', upstream: SERVICE_BASE_URLS.wallet, name: 'wallet-service' }, + { prefix: '/api/price', upstream: SERVICE_BASE_URLS.price, name: 'price-service' }, + { prefix: '/api/pairs', upstream: SERVICE_BASE_URLS.price, name: 'price-service' }, + { prefix: '/api/strategy', upstream: SERVICE_BASE_URLS.strategy, name: 'strategy-service' }, +].sort((a, b) => b.prefix.length - a.prefix.length); + const PROXY_TIMEOUT_MS = 5000; // ========================================================= -// SERVEUR HTTP NATIF (Node >= 18, pas de dépendances lourdes) +// HELPERS CORS // ========================================================= -const server = http.createServer(async (req, res) => { - const url = req.url || '/'; - - // ------------------------------------------------------- - // ROUTES INTERNES DU GATEWAY - // ------------------------------------------------------- +function applyCorsHeaders(headers) { + headers['access-control-allow-origin'] = '*'; + headers['access-control-allow-methods'] = 'GET,POST,PUT,PATCH,DELETE,OPTIONS'; + headers['access-control-allow-headers'] = 'Content-Type, Authorization'; + headers['access-control-max-age'] = '86400'; + return headers; +} - // GET /api/gateway/routes → table de routing (dev) - if (req.method === 'GET' && url === '/api/gateway/routes') { - return sendJSON(res, 200, { - ok: true, - data: { - gateway_port: GATEWAY_PORT, - path_rewrite: 'FULL PATH (path transmis tel quel aux services)', - routes: Object.entries(UPSTREAMS).map(([prefix, upstream]) => ({ - prefix, - upstream, - example: `${prefix}/... → ${upstream}${prefix}/...` - })) - } - }); - } +function sendJSON(res, statusCode, payload) { + const body = JSON.stringify(payload); + const headers = applyCorsHeaders({ + 'content-type': 'application/json; charset=utf-8', + 'content-length': Buffer.byteLength(body), + }); + res.writeHead(statusCode, headers); + res.end(body); +} - // GET /api/gateway/health → ping des services upstream - if (req.method === 'GET' && url === '/api/gateway/health') { - const checks = await Promise.all( - Object.entries(UPSTREAMS).map(async ([prefix, upstream]) => { - const healthUrl = `${upstream}/health`; - try { - const result = await proxyFetch('GET', healthUrl, null, 2000); - return { prefix, upstream, status: result.status, ok: result.status < 400 }; - } catch (e) { - return { prefix, upstream, status: 'DOWN', ok: false, error: e.message }; - } - }) - ); - const allOk = checks.every(c => c.ok); - return sendJSON(res, allOk ? 200 : 207, { ok: allOk, data: { services: checks } }); - } +// ========================================================= +// ROUTE MATCHING +// ========================================================= - // ------------------------------------------------------- - // PROXY VERS LES SERVICES - // ------------------------------------------------------- +function matchRoute(url = '') { + return ROUTES.find(r => url.startsWith(r.prefix)) || null; +} - // Trouver quel upstream correspond au path - const matchedPrefix = Object.keys(UPSTREAMS).find(prefix => url.startsWith(prefix)); +function pickHttpLib(protocol) { + return protocol === 'https:' ? https : http; +} - if (!matchedPrefix) { - return sendJSON(res, 404, { ok: false, error: { code: 'NOT_FOUND', message: `No route for ${url}` } }); - } +// Retire les hop-by-hop headers (RFC 7230) avant de transmettre +function cleanHopByHop(headers) { + const hopByHop = [ + 'connection', 'keep-alive', 'proxy-authenticate', + 'proxy-authorization', 'te', 'trailers', + 'transfer-encoding', 'upgrade', 'proxy-connection', + ]; + for (const h of hopByHop) delete headers[h]; +} - const upstreamBase = UPSTREAMS[matchedPrefix]; +// ========================================================= +// HEALTH CHECKS +// ========================================================= - // PATH REWRITE : on garde le path complet (les services exposent /api/...) - const targetUrl = upstreamBase + url; +function checkHealth(serviceName, baseUrl, timeoutMs = 1000) { + return new Promise(resolve => { + const upstream = new URL(baseUrl); + const lib = pickHttpLib(upstream.protocol); + + const r = lib.request({ + hostname: upstream.hostname, + port: upstream.port || (upstream.protocol === 'https:' ? 443 : 80), + method: 'GET', + path: '/health', + headers: { host: upstream.host }, + timeout: timeoutMs, + }, upRes => { + upRes.resume(); + resolve({ + service: serviceName, + url: baseUrl, + ok: (upRes.statusCode || 500) < 400, + status: upRes.statusCode, + }); + }); - // Lire le body pour les méthodes non-GET/HEAD - let body = null; - if (!['GET', 'HEAD'].includes(req.method)) { - body = await readBody(req); - } + r.on('timeout', () => { + r.destroy(); + resolve({ service: serviceName, url: baseUrl, ok: false, status: 'TIMEOUT' }); + }); + r.on('error', err => { + resolve({ service: serviceName, url: baseUrl, ok: false, status: 'DOWN', error: err.message }); + }); + r.end(); + }); +} - console.log(`[gateway] ${req.method} ${url} → ${targetUrl}`); +// ========================================================= +// HTTP PROXY +// ========================================================= - try { - const result = await proxyFetch(req.method, targetUrl, body, PROXY_TIMEOUT_MS, req.headers); +function proxyHttpRequest(req, res, route) { + const incomingUrl = req.url || '/'; + const upstream = new URL(route.upstream); + const lib = pickHttpLib(upstream.protocol); + const startedAt = Date.now(); + + const headers = { ...req.headers }; + headers.host = upstream.host; + headers['x-forwarded-host'] = req.headers.host || ''; + headers['x-forwarded-proto'] = 'http'; + headers['x-forwarded-for'] = req.socket?.remoteAddress || ''; + cleanHopByHop(headers); + + const upstreamReq = lib.request({ + hostname: upstream.hostname, + port: upstream.port || (upstream.protocol === 'https:' ? 443 : 80), + method: req.method, + path: incomingUrl, + headers, + timeout: PROXY_TIMEOUT_MS, + }, upRes => { + const outHeaders = applyCorsHeaders({ ...upRes.headers }); + outHeaders['x-gateway-upstream'] = route.name; + res.writeHead(upRes.statusCode || 500, outHeaders); + upRes.pipe(res); + + res.on('finish', () => { + const ms = Date.now() - startedAt; + console.log(`[gateway] ${req.method} ${incomingUrl} → ${route.upstream}${incomingUrl} (${upRes.statusCode}) ${ms}ms`); + }); + }); - // Retransmettre le statut et le body au client - res.writeHead(result.status, { 'Content-Type': 'application/json' }); - res.end(result.body); + upstreamReq.on('timeout', () => upstreamReq.destroy(new Error(`Timeout after ${PROXY_TIMEOUT_MS}ms`))); - console.log(`[gateway] ${req.method} ${url} → ${targetUrl} (${result.status})`); - } catch (err) { - // Service upstream injoignable → 502 - const serviceName = matchedPrefix.replace('/api/', '') + '-service'; - console.error(`[gateway] UPSTREAM_DOWN: ${targetUrl} — ${err.message}`); - return sendJSON(res, 502, { + upstreamReq.on('error', err => { + if (res.headersSent) { try { res.end(); } catch {} return; } + console.error(`[gateway] UPSTREAM_DOWN ${route.name}: ${err.message}`); + sendJSON(res, 502, { ok: false, - error: { - code: 'UPSTREAM_DOWN', - message: `${serviceName} unavailable` - } + error: { code: 'UPSTREAM_DOWN', message: `${route.name} unavailable` }, }); - } -}); + }); + + req.on('aborted', () => upstreamReq.destroy()); + res.on('close', () => upstreamReq.destroy()); + req.pipe(upstreamReq); +} // ========================================================= -// HELPERS +// WEBSOCKET PROXY (Socket.IO via /socket.io/*) // ========================================================= -/** - * Lit le body d'une requête entrante et retourne un Buffer - */ -function readBody(req) { - return new Promise((resolve, reject) => { - const chunks = []; - req.on('data', chunk => chunks.push(chunk)); - req.on('end', () => resolve(Buffer.concat(chunks))); - req.on('error', reject); - }); -} +function proxyWebSocketUpgrade(req, clientSocket, head) { + const incomingUrl = req.url || '/'; -/** - * Envoie une requête HTTP vers un upstream - * @param {string} method - * @param {string} url - * @param {Buffer|null} body - * @param {number} timeoutMs - * @param {object} [incomingHeaders] - * @returns {Promise<{status: number, body: string}>} - */ -function proxyFetch(method, url, body, timeoutMs = PROXY_TIMEOUT_MS, incomingHeaders = {}) { - return new Promise((resolve, reject) => { - const parsed = new URL(url); - const isHttps = parsed.protocol === 'https:'; - const lib = isHttps ? https : http; - - // Headers à transmettre (on filtre les headers problématiques) - const headers = { - 'Content-Type': 'application/json', - }; - if (incomingHeaders['authorization']) headers['Authorization'] = incomingHeaders['authorization']; - if (body && body.length > 0) headers['Content-Length'] = body.length; - - const options = { - hostname: parsed.hostname, - port: parsed.port || (isHttps ? 443 : 80), - path: parsed.pathname + parsed.search, - method, - headers, - timeout: timeoutMs, - }; - - const proxyReq = lib.request(options, (proxyRes) => { - const chunks = []; - proxyRes.on('data', chunk => chunks.push(chunk)); - proxyRes.on('end', () => { - resolve({ - status: proxyRes.statusCode, - body: Buffer.concat(chunks).toString('utf8') - }); - }); - }); + if (!incomingUrl.startsWith('/socket.io')) { + clientSocket.destroy(); + return; + } - proxyReq.on('timeout', () => { - proxyReq.destroy(); - reject(new Error(`Timeout after ${timeoutMs}ms`)); - }); + const upstream = new URL(SERVICE_BASE_URLS.alerts); + const port = Number(upstream.port) || (upstream.protocol === 'https:' ? 443 : 80); + const connect = upstream.protocol === 'https:' ? tls.connect : net.connect; + + const upstreamSocket = connect({ host: upstream.hostname, port }); - proxyReq.on('error', reject); + upstreamSocket.on('connect', () => { + const headers = { ...req.headers, host: upstream.host }; + delete headers['proxy-connection']; - if (body && body.length > 0) { - proxyReq.write(body); + let headerLines = ''; + for (const [key, value] of Object.entries(headers)) { + if (value === undefined) continue; + const v = Array.isArray(value) ? value.join(',') : String(value); + headerLines += `${key}: ${v}\r\n`; } - proxyReq.end(); + upstreamSocket.write(`${req.method || 'GET'} ${incomingUrl} HTTP/1.1\r\n` + headerLines + '\r\n'); + if (head && head.length > 0) upstreamSocket.write(head); + + upstreamSocket.pipe(clientSocket); + clientSocket.pipe(upstreamSocket); + clientSocket.resume(); + + console.log(`[gateway][ws] UPGRADE ${incomingUrl} → ${upstream.origin}${incomingUrl}`); }); -} -/** - * Envoie une réponse JSON - */ -function sendJSON(res, status, data) { - const body = JSON.stringify(data); - res.writeHead(status, { - 'Content-Type': 'application/json', - 'Content-Length': Buffer.byteLength(body) + upstreamSocket.on('error', err => { + console.error(`[gateway][ws] UPSTREAM_DOWN alerts-service: ${err.message}`); + try { clientSocket.end(); } catch {} }); - res.end(body); + + clientSocket.on('error', () => { try { upstreamSocket.end(); } catch {} }); } +// ========================================================= +// SERVEUR HTTP +// ========================================================= + +const server = http.createServer(async (req, res) => { + const url = req.url || '/'; + + // Preflight CORS + if (req.method === 'OPTIONS') { + res.writeHead(204, applyCorsHeaders({})); + res.end(); + return; + } + + // Health gateway + if (req.method === 'GET' && url === '/health') { + return sendJSON(res, 200, { ok: true, data: { service: 'gateway', port: GATEWAY_PORT } }); + } + + // Table de routing (dev) + if (req.method === 'GET' && url === '/api/gateway/routes') { + return sendJSON(res, 200, { + ok: true, + data: { + gateway_port: GATEWAY_PORT, + path_rewrite: 'FULL PATH (path transmis tel quel aux services)', + routes: ROUTES.map(r => ({ + prefix: r.prefix, + upstream: r.upstream, + service: r.name, + websocket: Boolean(r.ws), + })), + }, + }); + } + + // Health de tous les services + if (req.method === 'GET' && url === '/api/gateway/health') { + const checks = await Promise.all([ + checkHealth('price-service', SERVICE_BASE_URLS.price), + checkHealth('alerts-service', SERVICE_BASE_URLS.alerts), + checkHealth('wallet-service', SERVICE_BASE_URLS.wallet), + checkHealth('strategy-service', SERVICE_BASE_URLS.strategy), + ]); + + const required = ['price-service', 'alerts-service', 'wallet-service']; + const requiredOk = checks.filter(c => required.includes(c.service)).every(c => c.ok); + + return sendJSON(res, requiredOk ? 200 : 207, { + ok: requiredOk, + data: { + gateway: { ok: true, port: GATEWAY_PORT }, + services: checks.reduce((acc, c) => { acc[c.service] = c; return acc; }, {}), + }, + }); + } + + // Proxy vers les services + const route = matchRoute(url); + if (!route) { + return sendJSON(res, 404, { + ok: false, + error: { code: 'NOT_FOUND', message: `No route for ${url}` }, + }); + } + + return proxyHttpRequest(req, res, route); +}); + +// Proxy WebSocket (Socket.IO upgrade) +server.on('upgrade', (req, socket, head) => { + proxyWebSocketUpgrade(req, socket, head); +}); + // ========================================================= // DÉMARRAGE // ========================================================= @@ -225,14 +322,18 @@ server.listen(GATEWAY_PORT, () => { ╔══════════════════════════════════════════════════════════╗ ║ API GATEWAY - Wall-e-tte ║ ╠══════════════════════════════════════════════════════════╣ -║ Écoute sur : http://localhost:${GATEWAY_PORT} ║ +║ Écoute sur : http://localhost:${GATEWAY_PORT} ║ ║ ║ ║ ROUTING : ║ -║ /api/price/* → ${UPSTREAMS['/api/price']} ║ -║ /api/alerts/* → ${UPSTREAMS['/api/alerts']} ║ -║ /api/strategy/* → ${UPSTREAMS['/api/strategy']} ║ +║ /api/price/* → ${SERVICE_BASE_URLS.price} ║ +║ /api/pairs → ${SERVICE_BASE_URLS.price} ║ +║ /api/alerts/* → ${SERVICE_BASE_URLS.alerts} ║ +║ /socket.io/* → ${SERVICE_BASE_URLS.alerts} (WS+HTTP) ║ +║ /api/wallets/* → ${SERVICE_BASE_URLS.wallet} ║ +║ /api/strategy/* → ${SERVICE_BASE_URLS.strategy} ║ ║ ║ ║ ENDPOINTS GATEWAY : ║ +║ GET /health → gateway ok ║ ║ GET /api/gateway/routes → table de routing ║ ║ GET /api/gateway/health → état des services ║ ╚══════════════════════════════════════════════════════════╝`); diff --git a/Wallette/server/modules/price/server.js b/Wallette/server/modules/price/server.js index f652eec..1a1b882 100644 --- a/Wallette/server/modules/price/server.js +++ b/Wallette/server/modules/price/server.js @@ -6,7 +6,7 @@ import { getActivePairIdByCode, getPairById, listActivePairs } from "./repositor import { insertPricePoint, getCurrentPrice, getPriceHistory } from "./repositories/price.repository.js"; const app = express(); -const PORT = 3000; +const PORT = process.env.PRICE_PORT || 3001; // On fetch souvent pour construire OHLC (bougie 5 min) const SAMPLE_INTERVAL_MS = 10 * 1000; // 10s @@ -283,4 +283,4 @@ app.listen(PORT, async () => { } catch (e) { console.error("Erreur démarrage serveur:", e.message); } -}); \ No newline at end of file +}); diff --git a/Wallette/server/modules/strategy/server.js b/Wallette/server/modules/strategy/server.js new file mode 100644 index 0000000..7e6d8c7 --- /dev/null +++ b/Wallette/server/modules/strategy/server.js @@ -0,0 +1,80 @@ +// ========================================================= +// 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. +// ========================================================= + +import cors from 'cors'; +import dotenv from 'dotenv'; +import express from 'express'; +import path from 'path'; +import { fileURLToPath } from 'url'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = path.dirname(__filename); +dotenv.config({ path: path.resolve(__dirname, '../../.env') }); + +const PORT = process.env.STRATEGY_PORT || 3002; + +const app = express(); +app.use(cors()); +app.use(express.json()); + +// ========================================================= +// HEALTH CHECK (requis par le gateway) +// ========================================================= +app.get('/health', (req, res) => { + res.json({ + ok: true, + service: 'strategy-service', + port: PORT, + status: 'stub — module Sacha en cours d\'intégration', + }); +}); + +// ========================================================= +// STUB ROUTES /api/strategy/* +// ========================================================= +// 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', + }, + }); +}); + +// ========================================================= +// 404 +// ========================================================= +app.use((req, res) => { + res.status(404).json({ + ok: false, + error: { code: 'NOT_FOUND', message: 'Route non trouvée' }, + }); +}); + +// ========================================================= +// DÉMARRAGE +// ========================================================= +app.listen(PORT, () => { + console.log(` +╔══════════════════════════════════════════╗ +║ STRATEGY SERVICE (STUB) ║ +║ HTTP : http://localhost:${PORT} ║ +║ → /health = OK ║ +║ → /api/strategy/* = 501 ║ +╚══════════════════════════════════════════╝`); +}); diff --git a/Wallette/server/package.json b/Wallette/server/package.json index 1f06fd7..0da6dd7 100644 --- a/Wallette/server/package.json +++ b/Wallette/server/package.json @@ -10,7 +10,13 @@ "test": "node test-module-complet.js", "test:server": "node test-server-socket.js", "alerts": "node modules/alerts/server.js", - "alerts:dev": "node --watch modules/alerts/server.js" + "alerts:dev": "node --watch modules/alerts/server.js", + "wallet": "node modules/wallet/server.js", + "wallet:dev": "node --watch modules/wallet/server.js", + "price": "node modules/price/server.js", + "price:dev": "node --watch modules/price/server.js", + "strategy": "node modules/strategy/server.js", + "strategy:dev": "node --watch modules/strategy/server.js" }, "keywords": [ "crypto", diff --git a/Wallette/start-all.ps1 b/Wallette/start-all.ps1 index 6a978e2..151f08b 100644 --- a/Wallette/start-all.ps1 +++ b/Wallette/start-all.ps1 @@ -7,30 +7,48 @@ $ROOT = Split-Path -Parent $MyInvocation.MyCommand.Path Write-Host "[Wall-e-tte] Demarrage des services..." -ForegroundColor Cyan +# ─── Vérifier .env ───────────────────────────────────── +if (-not (Test-Path "$ROOT\server\.env")) { + Write-Host "" + Write-Host "[WARN] Aucun fichier server\.env detecte." -ForegroundColor Red + Write-Host " Copie .env.example vers server\.env puis complete DB_*." -ForegroundColor Red + Write-Host "" +} + # ─── PRICE SERVICE (port 3001) ─────────────────────────── -Write-Host "[1/4] Lancement price-service sur :3001" -ForegroundColor Yellow +Write-Host "[1/5] Lancement price-service sur :3001" -ForegroundColor Yellow Start-Process "cmd.exe" -ArgumentList "/k", "cd /d `"$ROOT\server`" && node modules/price/server.js" -WindowStyle Normal Start-Sleep -Seconds 2 # ─── ALERTS SERVICE (port 3003) ────────────────────────── -Write-Host "[2/4] Lancement alerts-service sur :3003" -ForegroundColor Yellow +Write-Host "[2/5] Lancement alerts-service sur :3003" -ForegroundColor Yellow Start-Process "cmd.exe" -ArgumentList "/k", "cd /d `"$ROOT\server`" && node modules/alerts/server.js" -WindowStyle Normal Start-Sleep -Seconds 2 -# ─── STRATEGY SERVICE (port 3002) - optionnel ───────────── +# ─── STRATEGY SERVICE (port 3002) ───────────────────────── $strategyPath = Join-Path $ROOT "server\modules\strategy\server.js" if (Test-Path $strategyPath) { - Write-Host "[3/4] Lancement strategy-service sur :3002" -ForegroundColor Yellow + Write-Host "[3/5] Lancement strategy-service sur :3002" -ForegroundColor Yellow Start-Process "cmd.exe" -ArgumentList "/k", "cd /d `"$ROOT\server`" && node modules/strategy/server.js" -WindowStyle Normal Start-Sleep -Seconds 2 } else { - Write-Host "[3/4] strategy-service non trouve - le gateway retournera 502 pour /api/strategy/*" -ForegroundColor DarkYellow + Write-Host "[3/5] strategy-service non trouve - le gateway retournera 502 pour /api/strategy/*" -ForegroundColor DarkYellow +} + +# ─── WALLET SERVICE (port 3004) ────────────────────────── +$walletPath = Join-Path $ROOT "server\modules\wallet\server.js" +if (Test-Path $walletPath) { + Write-Host "[4/5] Lancement wallet-service sur :3004" -ForegroundColor Yellow + Start-Process "cmd.exe" -ArgumentList "/k", "cd /d `"$ROOT\server`" && node modules/wallet/server.js" -WindowStyle Normal + Start-Sleep -Seconds 2 +} else { + Write-Host "[4/5] wallet-service non trouve - le gateway retournera 502 pour /api/wallets/*" -ForegroundColor DarkYellow } # ─── GATEWAY (port 3000) ───────────────────────────────── -Write-Host "[4/4] Lancement gateway sur :3000" -ForegroundColor Yellow +Write-Host "[5/5] Lancement gateway sur :3000" -ForegroundColor Yellow Start-Process "cmd.exe" -ArgumentList "/k", "cd /d `"$ROOT\gateway`" && node gateway.js" -WindowStyle Normal Write-Host "" @@ -38,11 +56,16 @@ Write-Host "Tous les services sont lances !" -ForegroundColor Green Write-Host "" Write-Host " Gateway : http://localhost:3000" -ForegroundColor Cyan Write-Host " Price : http://localhost:3001" -ForegroundColor Cyan +Write-Host " Strategy : http://localhost:3002" -ForegroundColor Cyan Write-Host " Alerts : http://localhost:3003" -ForegroundColor Cyan +Write-Host " Wallet : http://localhost:3004" -ForegroundColor Cyan Write-Host "" Write-Host "Tests rapides :" +Write-Host " curl http://localhost:3000/health" Write-Host " curl http://localhost:3000/api/gateway/health" Write-Host " curl 'http://localhost:3000/api/price/current?pair=BTC/EUR'" Write-Host " curl 'http://localhost:3000/api/alerts?userId=1'" +Write-Host " curl 'http://localhost:3000/api/wallets?userId=user-123'" +Write-Host " curl 'http://localhost:3000/api/strategy'" Write-Host "" Read-Host "Appuyez sur Entree pour fermer cette fenetre" diff --git a/Wallette/stop-all.bat b/Wallette/stop-all.bat new file mode 100644 index 0000000..e8dca43 --- /dev/null +++ b/Wallette/stop-all.bat @@ -0,0 +1,39 @@ +@echo off +REM ========================================================= +REM WALL-E-TTE - Arrêt de tous les micro-services +REM ========================================================= + +echo [Wall-e-tte] Arret des services en cours... +echo. + +REM ─── Tuer les process sur chaque port ──────────────────── + +echo Arret gateway :3000 +FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr ":3000 " ^| findstr "LISTENING"') DO ( + taskkill /PID %%P /F >nul 2>&1 +) + +echo Arret price :3001 +FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr ":3001 " ^| findstr "LISTENING"') DO ( + taskkill /PID %%P /F >nul 2>&1 +) + +echo Arret strategy :3002 +FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr ":3002 " ^| findstr "LISTENING"') DO ( + taskkill /PID %%P /F >nul 2>&1 +) + +echo Arret alerts :3003 +FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr ":3003 " ^| findstr "LISTENING"') DO ( + taskkill /PID %%P /F >nul 2>&1 +) + +echo Arret wallet :3004 +FOR /F "tokens=5" %%P IN ('netstat -ano ^| findstr ":3004 " ^| findstr "LISTENING"') DO ( + taskkill /PID %%P /F >nul 2>&1 +) + +echo. +echo [Wall-e-tte] Tous les services sont arretes. +echo. +pause diff --git a/Wallette/web/socketService.js b/Wallette/web/socketService.js index 2d57e27..54012f4 100644 --- a/Wallette/web/socketService.js +++ b/Wallette/web/socketService.js @@ -13,8 +13,8 @@ import { io } from 'socket.io-client'; // ─────────────────────────────────────────────────────────── // L'adresse du serveur (à changer selon l'environnement) -// En développement : localhost -// En production : l'adresse du vrai serveur +// On passe par le gateway :3000 qui proxifie le WebSocket +// vers le alerts-service :3003 automatiquement const SERVER_URL = 'http://localhost:3000'; // ─────────────────────────────────────────────────────────── @@ -31,28 +31,31 @@ let alertCallbacks = []; // FONCTION : Se connecter au serveur // ─────────────────────────────────────────────────────────── // Appelle cette fonction quand l'utilisateur se connecte à ton site -// +// // Paramètre : userId = l'identifiant de l'utilisateur connecté // // Exemple : connectToAlerts('user-123'); // ─────────────────────────────────────────────────────────── export function connectToAlerts(userId) { - + // Si déjà connecté, on ne fait rien if (socket !== null) { console.log('⚠️ Déjà connecté au serveur d\'alertes'); return; } - // Créer la connexion - socket = io(SERVER_URL); + // Créer la connexion via le gateway (qui proxifie /socket.io → :3003) + socket = io(SERVER_URL, { + path: '/socket.io', + transports: ['websocket', 'polling'] + }); // ───────────────────────────────────────────────────── // ÉVÉNEMENT : Connexion réussie // ───────────────────────────────────────────────────── socket.on('connect', function() { console.log('✅ Connecté au serveur d\'alertes'); - + // IMPORTANT : On envoie notre userId au serveur // Pour qu'il sache qui on est et nous envoie NOS alertes socket.emit('auth', userId); @@ -70,7 +73,7 @@ export function connectToAlerts(userId) { // ───────────────────────────────────────────────────── socket.on('alert', function(alert) { console.log('🔔 ALERTE REÇUE :', alert); - + // On appelle toutes les fonctions enregistrées // (voir onAlert plus bas) alertCallbacks.forEach(function(callback) {