From 2f32bfbab692d2ac712a977a100ded6bc5e1ae9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Fri, 17 Jul 2026 11:33:03 +0200 Subject: [PATCH 1/2] Fixing Dockerfile with missing files --- apps/parapharmacy-api/Dockerfile | 3 +++ 1 file changed, 3 insertions(+) diff --git a/apps/parapharmacy-api/Dockerfile b/apps/parapharmacy-api/Dockerfile index f036aab..10d0b80 100644 --- a/apps/parapharmacy-api/Dockerfile +++ b/apps/parapharmacy-api/Dockerfile @@ -36,6 +36,9 @@ RUN npm install --omit=dev # Copy source code COPY apps/parapharmacy-api/src/ ./src/ +# Copy scripts +COPY apps/parapharmacy-api/scripts/ ./scripts/ + # Expose port EXPOSE 3002 From e1e404440d693ded80c52bc154a07074ebe5d727 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Antoni=20Nu=C3=B1ez=20Romeu?= Date: Fri, 17 Jul 2026 11:40:13 +0200 Subject: [PATCH 2/2] First start n8n seed & activate --- docker-compose.yml | 14 ++++++- n8n/activate-workflows.js | 82 +++++++++++++++++++++++++++++++++++++++ n8n/init-import.sh | 40 ++++++++++++++++--- n8n/seed.json | 22 +++++++++++ 4 files changed, 152 insertions(+), 6 deletions(-) create mode 100644 n8n/activate-workflows.js create mode 100644 n8n/seed.json diff --git a/docker-compose.yml b/docker-compose.yml index dde6600..8510062 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -101,6 +101,12 @@ services: NODE_ENV: production MONGODB_URI: mongodb://mongodb:27017/parapharmacy CORS_ORIGIN: ${CORS_ORIGIN:-https://farmacias.hacecalor.net} + healthcheck: + test: ["CMD", "wget", "-qO", "/dev/null", "http://localhost:3002/api/health"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 15s depends_on: - mongodb @@ -152,7 +158,7 @@ services: depends_on: - postgres - # --- N8N Init: import workflows on first deploy --- + # --- N8N Init: import workflows, activate, and seed DB --- n8n-init: image: n8nio/n8n:latest entrypoint: ["/bin/sh", "/home/node/init-import.sh"] @@ -162,13 +168,19 @@ services: DB_POSTGRESDB_DATABASE: farmafinder DB_POSTGRESDB_USER: farmafinder DB_POSTGRESDB_PASSWORD: ${PG_PASSWORD:-change-me-in-production} + N8N_OWNER_EMAIL: ${N8N_EMAIL:-admin@farmafinder.com} + N8N_OWNER_PASSWORD: ${N8N_PASSWORD:-change-me} volumes: - n8n_data:/home/node/.n8n - ./n8n/workflows:/home/node/workflows - ./n8n/init-import.sh:/home/node/init-import.sh:ro + - ./n8n/activate-workflows.js:/home/node/activate-workflows.js:ro + - ./n8n/seed.json:/home/node/seed.json:ro depends_on: n8n: condition: service_healthy + parapharmacy-api: + condition: service_healthy volumes: backend_data: diff --git a/n8n/activate-workflows.js b/n8n/activate-workflows.js new file mode 100644 index 0000000..1fa9515 --- /dev/null +++ b/n8n/activate-workflows.js @@ -0,0 +1,82 @@ +// Activate all inactive n8n workflows via API. +// Usage: node activate-workflows.js +import http from 'node:http'; + +const [, , email, password] = process.argv; +if (!email || !password) { + console.error('Usage: node activate-workflows.js '); + process.exit(1); +} + +const HOST = 'n8n'; +const PORT = 5678; + +function request(method, path, body, cookie) { + return new Promise((resolve, reject) => { + const opts = { + hostname: HOST, port: PORT, path, method, + headers: { 'Content-Type': 'application/json' }, + }; + if (cookie) opts.headers['Cookie'] = cookie; + + const req = http.request(opts, (res) => { + const chunks = []; + res.on('data', (c) => chunks.push(c)); + res.on('end', () => { + const raw = Buffer.concat(chunks).toString(); + const setCookie = res.headers['set-cookie']; + let cookieVal = cookie; + if (setCookie) { + const match = setCookie.find((c) => c.startsWith('n8n-auth=')); + if (match) cookieVal = match.split(';')[0]; + } + let data; + try { data = JSON.parse(raw); } catch { data = raw; } + resolve({ status: res.statusCode, data, cookie: cookieVal }); + }); + }); + req.on('error', reject); + if (body) req.write(JSON.stringify(body)); + req.end(); + }); +} + +async function main() { + // 1. Login + console.log('[activate] Logging in...'); + const login = await request('POST', '/api/v1/login', { email, password }); + if (!login.cookie) { + console.error('[activate] Login failed — no session cookie.'); + process.exit(1); + } + console.log('[activate] Login OK.'); + + // 2. List workflows + const list = await request('GET', '/api/v1/workflows', null, login.cookie); + const workflows = list.data?.data || list.data || []; + if (!Array.isArray(workflows)) { + console.error('[activate] Unexpected response:', JSON.stringify(list.data).slice(0, 200)); + process.exit(1); + } + + // 3. Activate inactive ones + let activated = 0; + for (const wf of workflows) { + if (!wf.active) { + const res = await request('PATCH', `/api/v1/workflows/${wf.id}`, { active: true }, login.cookie); + if (res.status < 300) { + console.log(`[activate] Activated: ${wf.name || wf.id}`); + activated++; + } else { + console.error(`[activate] Failed to activate ${wf.id}: HTTP ${res.status}`); + } + } + } + + console.log(`[activate] Done. ${activated} workflow(s) activated.`); +} + +main().catch((err) => { + console.error('[activate] Error:', err.message); + process.exit(1); +}); diff --git a/n8n/init-import.sh b/n8n/init-import.sh index 29cc951..5f5500a 100755 --- a/n8n/init-import.sh +++ b/n8n/init-import.sh @@ -1,22 +1,52 @@ #!/bin/sh -# One-shot init: import workflows into n8n on first deploy. -# Skips if already imported (flag file in shared volume). +# One-shot init: import workflows, activate them, and seed the DB. +# Skips if already done (flag file in shared n8n_data volume). set -e FLAG="/home/node/.n8n/.workflows-imported" if [ -f "$FLAG" ]; then - echo "[n8n-init] Workflows already imported, skipping." + echo "[n8n-init] Already initialized, skipping." exit 0 fi +# ── 1. Wait for n8n ────────────────────────────────────────────────── echo "[n8n-init] Waiting for n8n to be ready..." until wget -qO /dev/null http://n8n:5678/healthz 2>/dev/null; do sleep 2 done +echo "[n8n-init] n8n is up." -echo "[n8n-init] Importing workflows from /home/node/workflows/..." +# ── 2. Import workflows ────────────────────────────────────────────── +echo "[n8n-init] Importing workflows..." n8n import:workflow --separate --input=/home/node/workflows/ +echo "[n8n-init] Workflows imported." +# ── 3. Activate workflows via n8n API (Node.js) ───────────────────── +if [ -f /home/node/activate-workflows.js ]; then + echo "[n8n-init] Activating workflows via n8n API..." + node /home/node/activate-workflows.js "${N8N_OWNER_EMAIL}" "${N8N_OWNER_PASSWORD}" || \ + echo "[n8n-init] WARNING: Workflow activation failed. Activate manually via n8n UI." +else + echo "[n8n-init] WARNING: activate-workflows.js not found, skipping activation." +fi + +# ── 4. Seed parapharmacy DB ────────────────────────────────────────── +echo "[n8n-init] Waiting for parapharmacy-api to be ready..." +until wget -qO /dev/null http://parapharmacy-api:3002/api/health 2>/dev/null; do + sleep 2 +done + +if [ -f /home/node/seed.json ]; then + echo "[n8n-init] Seeding parapharmacy products..." + SEED_RESP=$(wget -qO- --post-file=/home/node/seed.json \ + --header="Content-Type: application/json" \ + http://parapharmacy-api:3002/api/products/bulk 2>&1) + echo "[n8n-init] Seed result: $SEED_RESP" +else + echo "[n8n-init] WARNING: seed.json not found, skipping seed." +fi + +# ── Done ───────────────────────────────────────────────────────────── touch "$FLAG" -echo "[n8n-init] Import complete." +echo "[n8n-init] Initialization complete." diff --git a/n8n/seed.json b/n8n/seed.json new file mode 100644 index 0000000..f36a40d --- /dev/null +++ b/n8n/seed.json @@ -0,0 +1,22 @@ +[ + {"name":"Bioderma Atoderm Crema Hidratante 500ml","brand":"Bioderma","category":"Dermocosmética","price":18.95,"source":"seed","source_product_id":"seed_bioderma_atoderm","currency":"EUR","available":true}, + {"name":"La Roche-Posay Anthelios Airlicium FPS50+","brand":"La Roche-Posay","category":"Solar","price":19.95,"source":"seed","source_product_id":"seed_lrp_anthelios","currency":"EUR","available":true}, + {"name":"Mustela Gel de Ducha 500ml","brand":"Mustela","category":"Bebé","price":12.50,"source":"seed","source_product_id":"seed_mustela_gel","currency":"EUR","available":true}, + {"name":"Centrum Multivitaminicos 30 comprimidos","brand":"Centrum","category":"Vitaminas","price":15.80,"source":"seed","source_product_id":"seed_centrum_multi","currency":"EUR","available":true}, + {"name":"Dolocordalpan 1g Paracetamol 20 sobres","brand":"Dolocordalpan","category":"Analgésicos","price":4.95,"source":"seed","source_product_id":"seed_dolocordalpan","currency":"EUR","available":true}, + {"name":"Nurofen Flash 400mg 20 cápsulas","brand":"Nurofen","category":"Antiinflamatorios","price":6.75,"source":"seed","source_product_id":"seed_nurofen_flash","currency":"EUR","available":true}, + {"name":"Omeprazol Cinfa 20mg 28 cápsulas","brand":"Cinfa","category":"Gastrointestinal","price":8.50,"source":"seed","source_product_id":"seed_omeprazol_cinfa","currency":"EUR","available":true}, + {"name":"Vichy Mineral 89 Sérum Hidratante 30ml","brand":"Vichy","category":"Dermocosmética","price":25.90,"source":"seed","source_product_id":"seed_vichy_mineral89","currency":"EUR","available":true}, + {"name":"Avène Agua Termal 300ml","brand":"Avène","category":"Dermocosmética","price":9.95,"source":"seed","source_product_id":"seed_avene_agua","currency":"EUR","available":true}, + {"name":"CeraVe Crema Hidratante 340g","brand":"CeraVe","category":"Dermocosmética","price":14.95,"source":"seed","source_product_id":"seed_cerave_crema","currency":"EUR","available":true}, + {"name":"Ibuprofeno Alter 600mg 20 comprimidos","brand":"Alter","category":"Antiinflamatorios","price":5.20,"source":"seed","source_product_id":"seed_ibuprofeno_alter","currency":"EUR","available":true}, + {"name":"Salonpas Parches Analgésicos 5 unidades","brand":"Salonpas","category":"Analgésicos","price":7.80,"source":"seed","source_product_id":"seed_salonpas","currency":"EUR","available":true}, + {"name":"Fisiomer Spray Nasal 135ml","brand":"Fisiomer","category":"Respiratorio","price":11.50,"source":"seed","source_product_id":"seed_fisiomer","currency":"EUR","available":true}, + {"name":"Thealoz Duo Colirio 10ml","brand":"Thea","category":"Oftalmología","price":12.95,"source":"seed","source_product_id":"seed_thealoz","currency":"EUR","available":true}, + {"name":"Neutrogena Crema Manos Noruega 50ml","brand":"Neutrogena","category":"Dermocosmética","price":4.50,"source":"seed","source_product_id":"seed_neutrogena_manos","currency":"EUR","available":true}, + {"name":"Capricare 1 Leche en polvo 400g","brand":"Capricare","category":"Fórmulas lácteas","price":14.95,"source":"seed","source_product_id":"seed_capricare1","currency":"EUR","available":true}, + {"name":"Nutribén 2 Leche 800g","brand":"Nutribén","category":"Fórmulas lácteas","price":16.50,"source":"seed","source_product_id":"seed_nutriben2","currency":"EUR","available":true}, + {"name":"Bebelin Vitamina C 1g 20 comprimidos","brand":"Bebelín","category":"Vitaminas","price":6.95,"source":"seed","source_product_id":"seed_bebelin_vitc","currency":"EUR","available":true}, + {"name":"Lacer Pasta Dientes Sensibilidad 75ml","brand":"Lacer","category":"Oral","price":5.80,"source":"seed","source_product_id":"seed_lacer_pasta","currency":"EUR","available":true}, + {"name":"Fotosan Crema Solar FPS50 200ml","brand":"Fotosan","category":"Solar","price":22.50,"source":"seed","source_product_id":"seed_fotosan_solar","currency":"EUR","available":true} +]