diff --git a/docker-compose.yml b/docker-compose.yml index 00398e7..4fe744f 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -422,6 +422,35 @@ services: retries: 5 start_period: 60s + # ===================================== + # One-shot migrations runner (init job) + # ===================================== + migrations: + image: node:18 + container_name: pipeline_migrations + working_dir: /app + volumes: + - ./:/app + environment: + - POSTGRES_HOST=postgres + - POSTGRES_PORT=5432 + - POSTGRES_DB=dev_pipeline + - POSTGRES_USER=pipeline_admin + - POSTGRES_PASSWORD=${POSTGRES_PASSWORD} + - REDIS_HOST=redis + - REDIS_PORT=6379 + - REDIS_PASSWORD=${REDIS_PASSWORD} + - NODE_ENV=development + entrypoint: ["/bin/sh", "-c", "chmod +x ./scripts/migrate-all.sh && ./scripts/migrate-all.sh"] + depends_on: + postgres: + condition: service_healthy + redis: + condition: service_healthy + networks: + - pipeline_network + restart: "no" + # ===================================== # Enhanced Infrastructure for Code Generation # ===================================== @@ -544,6 +573,7 @@ services: - NODE_ENV=development - PORT=8000 - HOST=0.0.0.0 + - FRONTEND_URL=http://localhost:3000 # Database connections - POSTGRES_HOST=postgres - POSTGRES_PORT=5432 @@ -584,6 +614,8 @@ services: condition: service_healthy rabbitmq: condition: service_healthy + migrations: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8000/health"] interval: 30s @@ -623,6 +655,8 @@ services: condition: service_healthy mongodb: condition: service_started + migrations: + condition: service_completed_successfully tech-stack-selector: build: ./services/tech-stack-selector @@ -645,6 +679,8 @@ services: condition: service_healthy redis: condition: service_healthy + migrations: + condition: service_completed_successfully architecture-designer: build: ./services/architecture-designer @@ -670,6 +706,8 @@ services: condition: service_healthy mongodb: condition: service_started + migrations: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8003/health"] interval: 30s @@ -719,6 +757,8 @@ services: # condition: service_healthy chromadb: condition: service_healthy + migrations: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8004/health"] interval: 30s @@ -746,6 +786,8 @@ services: condition: service_healthy redis: condition: service_healthy + migrations: + condition: service_completed_successfully deployment-manager: build: ./services/deployment-manager @@ -773,6 +815,8 @@ services: condition: service_healthy mongodb: condition: service_started + migrations: + condition: service_completed_successfully user-auth: build: ./services/user-auth @@ -814,6 +858,8 @@ services: condition: service_healthy redis: condition: service_healthy + migrations: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8011/health"] interval: 30s @@ -852,6 +898,8 @@ services: condition: service_healthy redis: condition: service_healthy + migrations: + condition: service_completed_successfully healthcheck: test: ["CMD", "curl", "-f", "http://localhost:8009/health"] interval: 30s @@ -882,6 +930,8 @@ services: condition: service_healthy redis: condition: service_healthy + migrations: + condition: service_completed_successfully volumes: - ./self-improving-generator:/app - /tmp/generated-projects:/tmp/generated-projects @@ -925,6 +975,8 @@ services: condition: service_healthy self-improving-generator: condition: service_healthy + migrations: + condition: service_completed_successfully volumes: - /tmp/generated-projects:/tmp/generated-projects:ro diff --git a/package.json b/package.json new file mode 100644 index 0000000..3d6f9ef --- /dev/null +++ b/package.json @@ -0,0 +1,10 @@ +{ + "name": "codenuk-backend-live", + "private": true, + "version": "1.0.0", + "scripts": { + "migrate:all": "bash scripts/migrate-all.sh" + } +} + + diff --git a/scripts/migrate-all.sh b/scripts/migrate-all.sh new file mode 100755 index 0000000..f8a4534 --- /dev/null +++ b/scripts/migrate-all.sh @@ -0,0 +1,56 @@ +#!/usr/bin/env bash + +set -euo pipefail + +# This script runs database migrations for all services that define a migrate script. +# Ensure databases are reachable and required env vars are set (e.g., DATABASE_URL or individual PG vars). + +ROOT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)" + +# Default services. If arguments are provided, they will override this list. +default_services=( + "user-auth" + "template-manager" +) + +if [ "$#" -gt 0 ]; then + services=("$@") +else + services=("${default_services[@]}") +fi + +echo "Running migrations for services in: ${ROOT_DIR}/services" +echo "Target services: ${services[*]}" + +for service in "${services[@]}"; do + SERVICE_DIR="${ROOT_DIR}/services/${service}" + if [ ! -d "${SERVICE_DIR}" ]; then + echo "Skipping ${service}: directory not found at ${SERVICE_DIR}" >&2 + continue + fi + + if [ ! -f "${SERVICE_DIR}/package.json" ]; then + echo "Skipping ${service}: package.json not found" >&2 + continue + fi + + echo "\n========================================" + echo "āž”ļø ${service}: installing deps (ci)" + echo "========================================" + (cd "${SERVICE_DIR}" && npm ci --no-audit --no-fund --prefer-offline) + + echo "\n========================================" + echo "šŸš€ ${service}: running migrations" + echo "========================================" + + # Attempt to run migrate; if not defined, npm will error and we handle gracefully + if (cd "${SERVICE_DIR}" && npm run -s migrate); then + echo "āœ… ${service}: migrations completed" + else + echo "āš ļø ${service}: 'npm run migrate' failed or not defined; skipping" + fi +done + +echo "\nāœ… All migrations attempted. Review logs above for any errors." + + diff --git a/services/api-gateway/src/server.js b/services/api-gateway/src/server.js index 575ad29..2ddcef7 100644 --- a/services/api-gateway/src/server.js +++ b/services/api-gateway/src/server.js @@ -92,6 +92,7 @@ app.use('/api/websocket', express.json({ limit: '10mb' })); app.use('/api/gateway', express.json({ limit: '10mb' })); app.use('/api/auth', express.json({ limit: '10mb' })); app.use('/api/templates', express.json({ limit: '10mb' })); +app.use('/api/features', express.json({ limit: '10mb' })); app.use('/health', express.json({ limit: '10mb' })); // Trust proxy for accurate IP addresses @@ -220,8 +221,13 @@ app.use('/api', createServiceLimiter(1000)); console.log('šŸ”§ Registering /api/templates proxy route...'); app.use('/api/templates', createServiceLimiter(200), - authMiddleware.verifyToken, - authMiddleware.forwardUserContext, + // Conditionally require auth: allow public GETs, require token for write ops + (req, res, next) => { + if (req.method === 'GET') { + return next(); + } + return authMiddleware.verifyToken(req, res, () => authMiddleware.forwardUserContext(req, res, next)); + }, (req, res, next) => { const templateServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL; console.log(`šŸ”„ [TEMPLATE PROXY] ${req.method} ${req.originalUrl} → ${templateServiceUrl}${req.originalUrl}`); @@ -345,6 +351,72 @@ app.use('/api/self-improving', serviceRouter.createServiceProxy(serviceTargets.SELF_IMPROVING_GENERATOR_URL, 'self-improving-generator') ); +// Features (Template Manager) - expose /api/features via gateway +console.log('šŸ”§ Registering /api/features proxy route...'); +app.use('/api/features', + createServiceLimiter(300), + (req, res, next) => { + if (req.method === 'GET') { + return next(); + } + return authMiddleware.verifyToken(req, res, () => authMiddleware.forwardUserContext(req, res, next)); + }, + (req, res, next) => { + const templateServiceUrl = serviceTargets.TEMPLATE_MANAGER_URL; + console.log(`šŸ”„ [FEATURES PROXY] ${req.method} ${req.originalUrl} → ${templateServiceUrl}${req.originalUrl}`); + + res.setTimeout(15000, () => { + console.error('āŒ [FEATURES PROXY] Response timeout'); + if (!res.headersSent) { + res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' }); + } + }); + + const options = { + method: req.method, + url: `${templateServiceUrl}${req.originalUrl}`, + headers: { + 'Content-Type': 'application/json', + 'User-Agent': 'API-Gateway/1.0', + 'Connection': 'keep-alive', + 'Authorization': req.headers.authorization, + 'X-User-ID': req.user?.id || req.user?.userId, + 'X-User-Role': req.user?.role, + }, + timeout: 8000, + validateStatus: () => true, + maxRedirects: 0 + }; + + if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') { + options.data = req.body || {}; + console.log(`šŸ“¦ [FEATURES PROXY] Request body:`, JSON.stringify(req.body)); + } + + axios(options) + .then(response => { + console.log(`āœ… [FEATURES PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`); + if (!res.headersSent) { + res.status(response.status).json(response.data); + } + }) + .catch(error => { + console.error(`āŒ [FEATURES PROXY ERROR]:`, error.message); + if (!res.headersSent) { + if (error.response) { + res.status(error.response.status).json(error.response.data); + } else { + res.status(502).json({ + error: 'Template feature service unavailable', + message: error.code || error.message, + service: 'template-manager' + }); + } + } + }); + } +); + // Gateway management endpoints app.get('/api/gateway/info', authMiddleware.verifyToken, (req, res) => { res.json({