backend changes
This commit is contained in:
parent
86e68a2e6b
commit
f927bd3d3b
1937
dashboard-service/package-lock.json
generated
Normal file
1937
dashboard-service/package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
@ -545,7 +545,7 @@ services:
|
|||||||
- PORT=8000
|
- PORT=8000
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
- REDIS_PASSWORD=redis123
|
||||||
- POSTGRES_HOST=postgres
|
- POSTGRES_HOST=postgres
|
||||||
- POSTGRES_PORT=5432
|
- POSTGRES_PORT=5432
|
||||||
- POSTGRES_DB=dev_pipeline
|
- POSTGRES_DB=dev_pipeline
|
||||||
@ -555,6 +555,17 @@ services:
|
|||||||
- RABBITMQ_PORT=5672
|
- RABBITMQ_PORT=5672
|
||||||
- RABBITMQ_USER=pipeline_admin
|
- RABBITMQ_USER=pipeline_admin
|
||||||
- RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}
|
- RABBITMQ_PASSWORD=${RABBITMQ_PASSWORD}
|
||||||
|
# Internal service URLs for proxying (Docker network names)
|
||||||
|
- USER_AUTH_URL=http://user-auth:8011
|
||||||
|
- TEMPLATE_MANAGER_URL=http://template-manager:8009
|
||||||
|
- REQUIREMENT_PROCESSOR_URL=http://requirement-processor:8001
|
||||||
|
- TECH_STACK_SELECTOR_URL=http://tech-stack-selector:8002
|
||||||
|
- ARCHITECTURE_DESIGNER_URL=http://architecture-designer:8003
|
||||||
|
- CODE_GENERATOR_URL=http://code-generator:8004
|
||||||
|
- TEST_GENERATOR_URL=http://test-generator:8005
|
||||||
|
- DEPLOYMENT_MANAGER_URL=http://deployment-manager:8006
|
||||||
|
- DASHBOARD_URL=http://dashboard:8008
|
||||||
|
- SELF_IMPROVING_GENERATOR_URL=http://self-improving-generator:8007
|
||||||
networks:
|
networks:
|
||||||
- pipeline_network
|
- pipeline_network
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -578,7 +589,7 @@ services:
|
|||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
- REDIS_PASSWORD=redis123
|
||||||
- MONGODB_HOST=mongodb
|
- MONGODB_HOST=mongodb
|
||||||
- MONGODB_PORT=27017
|
- MONGODB_PORT=27017
|
||||||
- NEO4J_URI=bolt://neo4j:7687
|
- NEO4J_URI=bolt://neo4j:7687
|
||||||
@ -586,7 +597,7 @@ services:
|
|||||||
- NEO4J_PASSWORD=password
|
- NEO4J_PASSWORD=password
|
||||||
- CHROMA_HOST=chromadb
|
- CHROMA_HOST=chromadb
|
||||||
- CHROMA_PORT=8000
|
- CHROMA_PORT=8000
|
||||||
- REDIS_URL=redis://redis:6379
|
- REDIS_URL=redis://:redis123@redis:6379
|
||||||
networks:
|
networks:
|
||||||
- pipeline_network
|
- pipeline_network
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -610,7 +621,7 @@ services:
|
|||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
- REDIS_PASSWORD=redis123
|
||||||
networks:
|
networks:
|
||||||
- pipeline_network
|
- pipeline_network
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -666,7 +677,7 @@ services:
|
|||||||
- MONGODB_PORT=27017
|
- MONGODB_PORT=27017
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
- REDIS_PASSWORD=redis123
|
||||||
- CLAUDE_API_KEY=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
|
- CLAUDE_API_KEY=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
|
||||||
- OPENAI_API_KEY=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
|
- OPENAI_API_KEY=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
|
||||||
# - NEO4J_URI=bolt://neo4j:7687
|
# - NEO4J_URI=bolt://neo4j:7687
|
||||||
@ -711,7 +722,7 @@ services:
|
|||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
- REDIS_PASSWORD=redis123
|
||||||
networks:
|
networks:
|
||||||
- pipeline_network
|
- pipeline_network
|
||||||
depends_on:
|
depends_on:
|
||||||
@ -763,7 +774,7 @@ services:
|
|||||||
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
|
||||||
- REDIS_HOST=redis
|
- REDIS_HOST=redis
|
||||||
- REDIS_PORT=6379
|
- REDIS_PORT=6379
|
||||||
- REDIS_PASSWORD=${REDIS_PASSWORD}
|
- REDIS_PASSWORD=redis123
|
||||||
- JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
|
- JWT_ACCESS_SECRET=access-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
|
||||||
- JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
|
- JWT_REFRESH_SECRET=refresh-secret-key-2024-tech4biz-${POSTGRES_PASSWORD}
|
||||||
- JWT_ACCESS_EXPIRY=15m
|
- JWT_ACCESS_EXPIRY=15m
|
||||||
@ -878,7 +889,15 @@ services:
|
|||||||
- NODE_ENV=production
|
- NODE_ENV=production
|
||||||
- PORT=8008
|
- PORT=8008
|
||||||
- DATABASE_URL=postgresql://pipeline_admin:${POSTGRES_PASSWORD}@postgres:5432/dev_pipeline
|
- DATABASE_URL=postgresql://pipeline_admin:${POSTGRES_PASSWORD}@postgres:5432/dev_pipeline
|
||||||
- REDIS_URL=redis://pipeline_redis:6379
|
- POSTGRES_HOST=postgres
|
||||||
|
- POSTGRES_PORT=5432
|
||||||
|
- POSTGRES_DB=dev_pipeline
|
||||||
|
- POSTGRES_USER=pipeline_admin
|
||||||
|
- POSTGRES_PASSWORD=secure_pipeline_2024
|
||||||
|
- REDIS_URL=redis://:redis123@redis:6379
|
||||||
|
- REDIS_HOST=redis
|
||||||
|
- REDIS_PORT=6379
|
||||||
|
- REDIS_PASSWORD=redis123
|
||||||
- API_GATEWAY_URL=http://pipeline_api_gateway:8000
|
- API_GATEWAY_URL=http://pipeline_api_gateway:8000
|
||||||
- CODE_GENERATOR_URL=http://pipeline_code_generator:8004
|
- CODE_GENERATOR_URL=http://pipeline_code_generator:8004
|
||||||
- SELF_IMPROVING_URL=http://pipeline_self_improving_generator:8007
|
- SELF_IMPROVING_URL=http://pipeline_self_improving_generator:8007
|
||||||
|
|||||||
93
services/api-gateway/package-lock.json
generated
93
services/api-gateway/package-lock.json
generated
@ -15,6 +15,7 @@
|
|||||||
"express-rate-limit": "^6.8.1",
|
"express-rate-limit": "^6.8.1",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"helmet": "^7.0.0",
|
"helmet": "^7.0.0",
|
||||||
|
"http-proxy-middleware": "^3.0.5",
|
||||||
"jsonwebtoken": "^9.0.1",
|
"jsonwebtoken": "^9.0.1",
|
||||||
"morgan": "^1.10.0",
|
"morgan": "^1.10.0",
|
||||||
"pg": "^8.11.1",
|
"pg": "^8.11.1",
|
||||||
@ -1117,6 +1118,15 @@
|
|||||||
"@types/node": "*"
|
"@types/node": "*"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/@types/http-proxy": {
|
||||||
|
"version": "1.17.16",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/http-proxy/-/http-proxy-1.17.16.tgz",
|
||||||
|
"integrity": "sha512-sdWoUajOB1cd0A8cRRQ1cfyWNbmFKLAqBB89Y8x5iYyG/mkJHc0YUH8pdWBy2omi9qtCpiIgGjuwO0dQST2l5w==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/node": "*"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/@types/istanbul-lib-coverage": {
|
"node_modules/@types/istanbul-lib-coverage": {
|
||||||
"version": "2.0.6",
|
"version": "2.0.6",
|
||||||
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
"resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
|
||||||
@ -1493,7 +1503,6 @@
|
|||||||
"version": "3.0.3",
|
"version": "3.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz",
|
||||||
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
"integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"fill-range": "^7.1.1"
|
"fill-range": "^7.1.1"
|
||||||
@ -2260,6 +2269,12 @@
|
|||||||
"node": ">= 0.6"
|
"node": ">= 0.6"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/eventemitter3": {
|
||||||
|
"version": "4.0.7",
|
||||||
|
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||||
|
"integrity": "sha512-8guHBZCwKnFhYdHr2ysuRWErTwhoN2X8XELRlrRwpmfeY2jjuUN4taQMsULKUVo1K4DvZl+0pgfyoysHxvmvEw==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/execa": {
|
"node_modules/execa": {
|
||||||
"version": "5.1.1",
|
"version": "5.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz",
|
||||||
@ -2408,7 +2423,6 @@
|
|||||||
"version": "7.1.1",
|
"version": "7.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz",
|
||||||
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
"integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"to-regex-range": "^5.0.1"
|
"to-regex-range": "^5.0.1"
|
||||||
@ -2774,6 +2788,60 @@
|
|||||||
"node": ">= 0.8"
|
"node": ">= 0.8"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/http-proxy": {
|
||||||
|
"version": "1.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy/-/http-proxy-1.18.1.tgz",
|
||||||
|
"integrity": "sha512-7mz/721AbnJwIVbnaSv1Cz3Am0ZLT/UBwkC92VlxhXv/k/BBQfM2fXElQNC27BVGr0uwUpplYPQM9LnaBMR5NQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"eventemitter3": "^4.0.0",
|
||||||
|
"follow-redirects": "^1.0.0",
|
||||||
|
"requires-port": "^1.0.0"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=8.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware": {
|
||||||
|
"version": "3.0.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/http-proxy-middleware/-/http-proxy-middleware-3.0.5.tgz",
|
||||||
|
"integrity": "sha512-GLZZm1X38BPY4lkXA01jhwxvDoOkkXqjgVyUzVxiEK4iuRu03PZoYHhHRwxnfhQMDuaxi3vVri0YgSro/1oWqg==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"@types/http-proxy": "^1.17.15",
|
||||||
|
"debug": "^4.3.6",
|
||||||
|
"http-proxy": "^1.18.1",
|
||||||
|
"is-glob": "^4.0.3",
|
||||||
|
"is-plain-object": "^5.0.0",
|
||||||
|
"micromatch": "^4.0.8"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": "^14.15.0 || ^16.10.0 || >=18.0.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware/node_modules/debug": {
|
||||||
|
"version": "4.4.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/debug/-/debug-4.4.1.tgz",
|
||||||
|
"integrity": "sha512-KcKCqiftBJcZr++7ykoDIEwSa3XWowTfNPo92BYxjXiyYEVrUQh2aLyhxBCwww+heortUFxEJYcRzosstTEBYQ==",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {
|
||||||
|
"ms": "^2.1.3"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=6.0"
|
||||||
|
},
|
||||||
|
"peerDependenciesMeta": {
|
||||||
|
"supports-color": {
|
||||||
|
"optional": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/http-proxy-middleware/node_modules/ms": {
|
||||||
|
"version": "2.1.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
|
||||||
|
"integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/human-signals": {
|
"node_modules/human-signals": {
|
||||||
"version": "2.1.0",
|
"version": "2.1.0",
|
||||||
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
"resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz",
|
||||||
@ -2900,7 +2968,6 @@
|
|||||||
"version": "2.1.1",
|
"version": "2.1.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
|
||||||
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
"integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
@ -2930,7 +2997,6 @@
|
|||||||
"version": "4.0.3",
|
"version": "4.0.3",
|
||||||
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
"resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
|
||||||
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
"integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-extglob": "^2.1.1"
|
"is-extglob": "^2.1.1"
|
||||||
@ -2943,12 +3009,20 @@
|
|||||||
"version": "7.0.0",
|
"version": "7.0.0",
|
||||||
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
"resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
|
||||||
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
"integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=0.12.0"
|
"node": ">=0.12.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/is-plain-object": {
|
||||||
|
"version": "5.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-5.0.0.tgz",
|
||||||
|
"integrity": "sha512-VRSzKkbMm5jMDoKLbltAkFQ5Qr7VDiTFGXxYFXXowVj387GeGNOCsOH6Msy00SGZ3Fp84b1Naa1psqgcCIEP5Q==",
|
||||||
|
"license": "MIT",
|
||||||
|
"engines": {
|
||||||
|
"node": ">=0.10.0"
|
||||||
|
}
|
||||||
|
},
|
||||||
"node_modules/is-stream": {
|
"node_modules/is-stream": {
|
||||||
"version": "2.0.1",
|
"version": "2.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz",
|
||||||
@ -4001,7 +4075,6 @@
|
|||||||
"version": "4.0.8",
|
"version": "4.0.8",
|
||||||
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
"resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz",
|
||||||
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
"integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"braces": "^3.0.3",
|
"braces": "^3.0.3",
|
||||||
@ -4547,7 +4620,6 @@
|
|||||||
"version": "2.3.1",
|
"version": "2.3.1",
|
||||||
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
"resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
|
||||||
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
"integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=8.6"
|
"node": ">=8.6"
|
||||||
@ -4803,6 +4875,12 @@
|
|||||||
"node": ">=0.10.0"
|
"node": ">=0.10.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
"node_modules/requires-port": {
|
||||||
|
"version": "1.0.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/requires-port/-/requires-port-1.0.0.tgz",
|
||||||
|
"integrity": "sha512-KigOCHcocU3XODJxsu8i/j8T9tzT4adHiecwORRQ0ZZFcp7ahwXuRU1m+yuO90C5ZUyGeGfocHDI14M3L3yDAQ==",
|
||||||
|
"license": "MIT"
|
||||||
|
},
|
||||||
"node_modules/resolve": {
|
"node_modules/resolve": {
|
||||||
"version": "1.22.10",
|
"version": "1.22.10",
|
||||||
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz",
|
||||||
@ -5442,7 +5520,6 @@
|
|||||||
"version": "5.0.1",
|
"version": "5.0.1",
|
||||||
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
"resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
|
||||||
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
"integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
|
||||||
"dev": true,
|
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"is-number": "^7.0.0"
|
"is-number": "^7.0.0"
|
||||||
|
|||||||
@ -9,22 +9,23 @@
|
|||||||
"test": "jest"
|
"test": "jest"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"express": "^4.18.2",
|
|
||||||
"cors": "^2.8.5",
|
|
||||||
"helmet": "^7.0.0",
|
|
||||||
"express-rate-limit": "^6.8.1",
|
|
||||||
"jsonwebtoken": "^9.0.1",
|
|
||||||
"redis": "^4.6.7",
|
|
||||||
"pg": "^8.11.1",
|
|
||||||
"axios": "^1.4.0",
|
"axios": "^1.4.0",
|
||||||
"winston": "^3.10.0",
|
"cors": "^2.8.5",
|
||||||
"socket.io": "^4.7.2",
|
|
||||||
"dotenv": "^16.3.1",
|
"dotenv": "^16.3.1",
|
||||||
|
"express": "^4.18.2",
|
||||||
|
"express-rate-limit": "^6.8.1",
|
||||||
"express-validator": "^7.0.1",
|
"express-validator": "^7.0.1",
|
||||||
"morgan": "^1.10.0"
|
"helmet": "^7.0.0",
|
||||||
|
"http-proxy-middleware": "^2.0.6",
|
||||||
|
"jsonwebtoken": "^9.0.1",
|
||||||
|
"morgan": "^1.10.0",
|
||||||
|
"pg": "^8.11.1",
|
||||||
|
"redis": "^4.6.7",
|
||||||
|
"socket.io": "^4.7.2",
|
||||||
|
"winston": "^3.10.0"
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"nodemon": "^3.0.1",
|
"jest": "^29.6.1",
|
||||||
"jest": "^29.6.1"
|
"nodemon": "^3.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
122
services/api-gateway/src/middleware/authentication.js
Normal file
122
services/api-gateway/src/middleware/authentication.js
Normal file
@ -0,0 +1,122 @@
|
|||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
// JWT token verification middleware
|
||||||
|
const verifyToken = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Access token required',
|
||||||
|
error: 'No token provided'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Verify JWT token
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
req.user = decoded;
|
||||||
|
|
||||||
|
// Add user context to headers for downstream services
|
||||||
|
req.headers['x-user-id'] = decoded.id || decoded.userId;
|
||||||
|
req.headers['x-user-email'] = decoded.email;
|
||||||
|
req.headers['x-user-role'] = decoded.role || 'user';
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('Token verification failed:', error.message);
|
||||||
|
|
||||||
|
if (error.name === 'TokenExpiredError') {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Token expired',
|
||||||
|
error: 'Please login again'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (error.name === 'JsonWebTokenError') {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Invalid token',
|
||||||
|
error: 'Token verification failed'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Authentication failed',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Forward user context to downstream services
|
||||||
|
const forwardUserContext = (req, res, next) => {
|
||||||
|
if (req.user) {
|
||||||
|
// Add gateway headers for service identification
|
||||||
|
req.headers['x-gateway-request-id'] = req.requestId;
|
||||||
|
req.headers['x-gateway-timestamp'] = new Date().toISOString();
|
||||||
|
req.headers['x-forwarded-by'] = 'api-gateway';
|
||||||
|
req.headers['x-forwarded-for'] = req.ip;
|
||||||
|
req.headers['x-forwarded-proto'] = req.protocol;
|
||||||
|
req.headers['x-forwarded-host'] = req.get('host');
|
||||||
|
}
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Optional token verification (doesn't fail if no token)
|
||||||
|
const verifyTokenOptional = async (req, res, next) => {
|
||||||
|
try {
|
||||||
|
const token = req.headers.authorization?.split(' ')[1];
|
||||||
|
|
||||||
|
if (token) {
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
req.user = decoded;
|
||||||
|
|
||||||
|
// Add user context to headers
|
||||||
|
req.headers['x-user-id'] = decoded.id || decoded.userId;
|
||||||
|
req.headers['x-user-email'] = decoded.email;
|
||||||
|
req.headers['x-user-role'] = decoded.role || 'user';
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
// Continue without authentication for optional routes
|
||||||
|
console.log('Optional token verification failed:', error.message);
|
||||||
|
next();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// Role-based authorization middleware
|
||||||
|
const requireRole = (roles) => {
|
||||||
|
return (req, res, next) => {
|
||||||
|
if (!req.user) {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Authentication required'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRole = req.user.role || 'user';
|
||||||
|
const allowedRoles = Array.isArray(roles) ? roles : [roles];
|
||||||
|
|
||||||
|
if (!allowedRoles.includes(userRole)) {
|
||||||
|
return res.status(403).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Insufficient permissions',
|
||||||
|
required_roles: allowedRoles,
|
||||||
|
user_role: userRole
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
verifyToken,
|
||||||
|
forwardUserContext,
|
||||||
|
verifyTokenOptional,
|
||||||
|
requireRole
|
||||||
|
};
|
||||||
126
services/api-gateway/src/middleware/requestLogger.js
Normal file
126
services/api-gateway/src/middleware/requestLogger.js
Normal file
@ -0,0 +1,126 @@
|
|||||||
|
const winston = require('winston');
|
||||||
|
|
||||||
|
// Configure Winston logger
|
||||||
|
const logger = winston.createLogger({
|
||||||
|
level: process.env.LOG_LEVEL || 'info',
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.timestamp(),
|
||||||
|
winston.format.errors({ stack: true }),
|
||||||
|
winston.format.json()
|
||||||
|
),
|
||||||
|
defaultMeta: { service: 'api-gateway' },
|
||||||
|
transports: [
|
||||||
|
new winston.transports.File({ filename: 'logs/error.log', level: 'error' }),
|
||||||
|
new winston.transports.File({ filename: 'logs/combined.log' }),
|
||||||
|
new winston.transports.Console({
|
||||||
|
format: winston.format.combine(
|
||||||
|
winston.format.colorize(),
|
||||||
|
winston.format.simple()
|
||||||
|
)
|
||||||
|
})
|
||||||
|
]
|
||||||
|
});
|
||||||
|
|
||||||
|
// Request logging middleware
|
||||||
|
const logRequest = (req, res, next) => {
|
||||||
|
const startTime = Date.now();
|
||||||
|
|
||||||
|
// Log incoming request
|
||||||
|
logger.info('Incoming Request', {
|
||||||
|
requestId: req.requestId,
|
||||||
|
method: req.method,
|
||||||
|
url: req.originalUrl,
|
||||||
|
ip: req.ip,
|
||||||
|
userAgent: req.get('User-Agent'),
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
headers: {
|
||||||
|
authorization: req.headers.authorization ? '[REDACTED]' : undefined,
|
||||||
|
'content-type': req.headers['content-type'],
|
||||||
|
'x-forwarded-for': req.headers['x-forwarded-for']
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override res.end to log response
|
||||||
|
const originalEnd = res.end;
|
||||||
|
res.end = function(chunk, encoding) {
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
// Log response
|
||||||
|
logger.info('Response Sent', {
|
||||||
|
requestId: req.requestId,
|
||||||
|
method: req.method,
|
||||||
|
url: req.originalUrl,
|
||||||
|
statusCode: res.statusCode,
|
||||||
|
responseTime: `${responseTime}ms`,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
// Call original end method
|
||||||
|
originalEnd.call(this, chunk, encoding);
|
||||||
|
};
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
|
||||||
|
// Error logging middleware
|
||||||
|
const logError = (error, req, res, next) => {
|
||||||
|
logger.error('Request Error', {
|
||||||
|
requestId: req.requestId,
|
||||||
|
method: req.method,
|
||||||
|
url: req.originalUrl,
|
||||||
|
error: error.message,
|
||||||
|
stack: error.stack,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
next(error);
|
||||||
|
};
|
||||||
|
|
||||||
|
// Service proxy logging
|
||||||
|
const logProxyRequest = (serviceName, targetUrl) => {
|
||||||
|
return (proxyReq, req, res) => {
|
||||||
|
logger.info('Proxy Request', {
|
||||||
|
requestId: req.requestId,
|
||||||
|
service: serviceName,
|
||||||
|
method: req.method,
|
||||||
|
originalUrl: req.originalUrl,
|
||||||
|
targetUrl: targetUrl,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const logProxyResponse = (serviceName) => {
|
||||||
|
return (proxyRes, req, res) => {
|
||||||
|
logger.info('Proxy Response', {
|
||||||
|
requestId: req.requestId,
|
||||||
|
service: serviceName,
|
||||||
|
method: req.method,
|
||||||
|
originalUrl: req.originalUrl,
|
||||||
|
statusCode: proxyRes.statusCode,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
const logProxyError = (serviceName) => {
|
||||||
|
return (err, req, res) => {
|
||||||
|
logger.error('Proxy Error', {
|
||||||
|
requestId: req.requestId,
|
||||||
|
service: serviceName,
|
||||||
|
method: req.method,
|
||||||
|
originalUrl: req.originalUrl,
|
||||||
|
error: err.message,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
logger,
|
||||||
|
logRequest,
|
||||||
|
logError,
|
||||||
|
logProxyRequest,
|
||||||
|
logProxyResponse,
|
||||||
|
logProxyError
|
||||||
|
};
|
||||||
140
services/api-gateway/src/middleware/serviceHealth.js
Normal file
140
services/api-gateway/src/middleware/serviceHealth.js
Normal file
@ -0,0 +1,140 @@
|
|||||||
|
const axios = require('axios');
|
||||||
|
|
||||||
|
// Service health monitoring
|
||||||
|
class ServiceHealthMonitor {
|
||||||
|
constructor() {
|
||||||
|
this.serviceStatus = {};
|
||||||
|
this.healthCheckInterval = null;
|
||||||
|
this.healthCheckFrequency = parseInt(process.env.HEALTH_CHECK_FREQUENCY) || 30000; // 30 seconds
|
||||||
|
}
|
||||||
|
|
||||||
|
// Initialize health monitoring for all services
|
||||||
|
async initializeHealthMonitoring() {
|
||||||
|
const serviceTargets = {
|
||||||
|
USER_AUTH_URL: process.env.USER_AUTH_URL || 'http://localhost:8011',
|
||||||
|
TEMPLATE_MANAGER_URL: process.env.TEMPLATE_MANAGER_URL || 'http://localhost:8009',
|
||||||
|
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001',
|
||||||
|
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://localhost:8002',
|
||||||
|
ARCHITECTURE_DESIGNER_URL: process.env.ARCHITECTURE_DESIGNER_URL || 'http://localhost:8003',
|
||||||
|
CODE_GENERATOR_URL: process.env.CODE_GENERATOR_URL || 'http://localhost:8004',
|
||||||
|
TEST_GENERATOR_URL: process.env.TEST_GENERATOR_URL || 'http://localhost:8005',
|
||||||
|
DEPLOYMENT_MANAGER_URL: process.env.DEPLOYMENT_MANAGER_URL || 'http://localhost:8006',
|
||||||
|
DASHBOARD_URL: process.env.DASHBOARD_URL || 'http://localhost:8008',
|
||||||
|
SELF_IMPROVING_GENERATOR_URL: process.env.SELF_IMPROVING_GENERATOR_URL || 'http://localhost:8007',
|
||||||
|
};
|
||||||
|
|
||||||
|
// Initial health check
|
||||||
|
await this.checkAllServices(serviceTargets);
|
||||||
|
|
||||||
|
// Start periodic health checks
|
||||||
|
this.startPeriodicHealthChecks(serviceTargets);
|
||||||
|
|
||||||
|
console.log('✅ Service health monitoring initialized');
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check health of all services
|
||||||
|
async checkAllServices(serviceTargets) {
|
||||||
|
const healthPromises = Object.entries(serviceTargets).map(async ([serviceName, serviceUrl]) => {
|
||||||
|
try {
|
||||||
|
const startTime = Date.now();
|
||||||
|
const response = await axios.get(`${serviceUrl}/health`, {
|
||||||
|
timeout: 5000,
|
||||||
|
headers: { 'User-Agent': 'API-Gateway-Health-Check' }
|
||||||
|
});
|
||||||
|
|
||||||
|
const responseTime = Date.now() - startTime;
|
||||||
|
|
||||||
|
this.serviceStatus[serviceName] = {
|
||||||
|
status: response.status === 200 ? 'healthy' : 'unhealthy',
|
||||||
|
url: serviceUrl,
|
||||||
|
responseTime: responseTime,
|
||||||
|
lastChecked: new Date().toISOString(),
|
||||||
|
error: null
|
||||||
|
};
|
||||||
|
} catch (error) {
|
||||||
|
this.serviceStatus[serviceName] = {
|
||||||
|
status: 'unhealthy',
|
||||||
|
url: serviceUrl,
|
||||||
|
responseTime: null,
|
||||||
|
lastChecked: new Date().toISOString(),
|
||||||
|
error: error.message
|
||||||
|
};
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
await Promise.allSettled(healthPromises);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Start periodic health checks
|
||||||
|
startPeriodicHealthChecks(serviceTargets) {
|
||||||
|
if (this.healthCheckInterval) {
|
||||||
|
clearInterval(this.healthCheckInterval);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.healthCheckInterval = setInterval(async () => {
|
||||||
|
await this.checkAllServices(serviceTargets);
|
||||||
|
}, this.healthCheckFrequency);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Stop health monitoring
|
||||||
|
stopHealthMonitoring() {
|
||||||
|
if (this.healthCheckInterval) {
|
||||||
|
clearInterval(this.healthCheckInterval);
|
||||||
|
this.healthCheckInterval = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get current service status
|
||||||
|
getServiceStatus() {
|
||||||
|
const totalServices = Object.keys(this.serviceStatus).length;
|
||||||
|
const healthyServices = Object.values(this.serviceStatus).filter(s => s.status === 'healthy').length;
|
||||||
|
|
||||||
|
return {
|
||||||
|
summary: {
|
||||||
|
total_services: totalServices,
|
||||||
|
healthy_services: healthyServices,
|
||||||
|
unhealthy_services: totalServices - healthyServices,
|
||||||
|
overall_health: healthyServices === totalServices ? 'healthy' : 'degraded'
|
||||||
|
},
|
||||||
|
services: this.serviceStatus,
|
||||||
|
last_updated: new Date().toISOString()
|
||||||
|
};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Create singleton instance
|
||||||
|
const healthMonitor = new ServiceHealthMonitor();
|
||||||
|
|
||||||
|
// Middleware to get service status
|
||||||
|
const getServiceStatus = (req, res) => {
|
||||||
|
const status = healthMonitor.getServiceStatus();
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
...status
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Middleware to check if a specific service is healthy
|
||||||
|
const checkServiceHealth = (serviceName) => {
|
||||||
|
return (req, res, next) => {
|
||||||
|
const serviceStatus = healthMonitor.serviceStatus[serviceName];
|
||||||
|
|
||||||
|
if (!serviceStatus || serviceStatus.status !== 'healthy') {
|
||||||
|
return res.status(503).json({
|
||||||
|
success: false,
|
||||||
|
message: `Service ${serviceName} is currently unavailable`,
|
||||||
|
service_status: serviceStatus || { status: 'unknown' },
|
||||||
|
retry_after: 30
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
next();
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
healthMonitor,
|
||||||
|
initializeHealthMonitoring: () => healthMonitor.initializeHealthMonitoring(),
|
||||||
|
getServiceStatus,
|
||||||
|
checkServiceHealth
|
||||||
|
};
|
||||||
136
services/api-gateway/src/middleware/webSocket.js
Normal file
136
services/api-gateway/src/middleware/webSocket.js
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
const jwt = require('jsonwebtoken');
|
||||||
|
|
||||||
|
// WebSocket authentication middleware
|
||||||
|
const authenticateSocket = (socket, next) => {
|
||||||
|
try {
|
||||||
|
const token = socket.handshake.auth.token || socket.handshake.headers.authorization?.split(' ')[1];
|
||||||
|
|
||||||
|
if (!token) {
|
||||||
|
return next(new Error('Authentication token required'));
|
||||||
|
}
|
||||||
|
|
||||||
|
const decoded = jwt.verify(token, process.env.JWT_SECRET);
|
||||||
|
socket.user = decoded;
|
||||||
|
socket.userId = decoded.id || decoded.userId;
|
||||||
|
|
||||||
|
console.log(`✅ WebSocket authenticated: ${socket.user.email || socket.userId}`);
|
||||||
|
next();
|
||||||
|
} catch (error) {
|
||||||
|
console.error('WebSocket authentication failed:', error.message);
|
||||||
|
next(new Error('Authentication failed'));
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
// WebSocket connection handler with authentication
|
||||||
|
const handleWebSocketConnections = (io) => {
|
||||||
|
// Authentication middleware for all connections
|
||||||
|
io.use(authenticateSocket);
|
||||||
|
|
||||||
|
io.on('connection', (socket) => {
|
||||||
|
console.log(`🔌 Client connected: ${socket.id} (User: ${socket.user.email || socket.userId})`);
|
||||||
|
|
||||||
|
// Join user-specific room
|
||||||
|
const userRoom = `user_${socket.userId}`;
|
||||||
|
socket.join(userRoom);
|
||||||
|
|
||||||
|
// Send connection confirmation
|
||||||
|
socket.emit('connected', {
|
||||||
|
message: 'Connected to CodeNuk API Gateway',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
socketId: socket.id,
|
||||||
|
user: {
|
||||||
|
id: socket.userId,
|
||||||
|
email: socket.user.email,
|
||||||
|
role: socket.user.role
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle service-specific events
|
||||||
|
socket.on('subscribe_to_service', (data) => {
|
||||||
|
const { service } = data;
|
||||||
|
const serviceRoom = `service_${service}`;
|
||||||
|
socket.join(serviceRoom);
|
||||||
|
|
||||||
|
console.log(`📡 User ${socket.userId} subscribed to ${service} updates`);
|
||||||
|
socket.emit('subscribed', { service, room: serviceRoom });
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('unsubscribe_from_service', (data) => {
|
||||||
|
const { service } = data;
|
||||||
|
const serviceRoom = `service_${service}`;
|
||||||
|
socket.leave(serviceRoom);
|
||||||
|
|
||||||
|
console.log(`📡 User ${socket.userId} unsubscribed from ${service} updates`);
|
||||||
|
socket.emit('unsubscribed', { service, room: serviceRoom });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle project-specific events
|
||||||
|
socket.on('join_project', (data) => {
|
||||||
|
const { projectId } = data;
|
||||||
|
const projectRoom = `project_${projectId}`;
|
||||||
|
socket.join(projectRoom);
|
||||||
|
|
||||||
|
console.log(`📁 User ${socket.userId} joined project ${projectId}`);
|
||||||
|
socket.emit('joined_project', { projectId, room: projectRoom });
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('leave_project', (data) => {
|
||||||
|
const { projectId } = data;
|
||||||
|
const projectRoom = `project_${projectId}`;
|
||||||
|
socket.leave(projectRoom);
|
||||||
|
|
||||||
|
console.log(`📁 User ${socket.userId} left project ${projectId}`);
|
||||||
|
socket.emit('left_project', { projectId, room: projectRoom });
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle real-time notifications
|
||||||
|
socket.on('send_notification', (data) => {
|
||||||
|
const { targetUserId, message, type } = data;
|
||||||
|
const targetRoom = `user_${targetUserId}`;
|
||||||
|
|
||||||
|
io.to(targetRoom).emit('notification', {
|
||||||
|
from: socket.userId,
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log(`📢 Notification sent from ${socket.userId} to ${targetUserId}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle disconnection
|
||||||
|
socket.on('disconnect', (reason) => {
|
||||||
|
console.log(`🔌 Client disconnected: ${socket.id} (User: ${socket.user.email || socket.userId}) - Reason: ${reason}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
// Handle errors
|
||||||
|
socket.on('error', (error) => {
|
||||||
|
console.error(`❌ Socket error for ${socket.id}:`, error);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Broadcast service status updates
|
||||||
|
const broadcastServiceUpdate = (serviceName, status) => {
|
||||||
|
io.to(`service_${serviceName}`).emit('service_status_update', {
|
||||||
|
service: serviceName,
|
||||||
|
status,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Broadcast project updates
|
||||||
|
const broadcastProjectUpdate = (projectId, update) => {
|
||||||
|
io.to(`project_${projectId}`).emit('project_update', {
|
||||||
|
projectId,
|
||||||
|
update,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
return {
|
||||||
|
broadcastServiceUpdate,
|
||||||
|
broadcastProjectUpdate
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = handleWebSocketConnections;
|
||||||
68
services/api-gateway/src/routes/healthRouter.js
Normal file
68
services/api-gateway/src/routes/healthRouter.js
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { healthMonitor } = require('../middleware/serviceHealth');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Get comprehensive service health status
|
||||||
|
router.get('/services', (req, res) => {
|
||||||
|
const status = healthMonitor.getServiceStatus();
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
...status
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Get health status for a specific service
|
||||||
|
router.get('/service/:serviceName', (req, res) => {
|
||||||
|
const { serviceName } = req.params;
|
||||||
|
const serviceStatus = healthMonitor.serviceStatus[serviceName];
|
||||||
|
|
||||||
|
if (!serviceStatus) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
message: `Service ${serviceName} not found`,
|
||||||
|
available_services: Object.keys(healthMonitor.serviceStatus)
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
service: serviceName,
|
||||||
|
...serviceStatus
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Trigger manual health check for all services
|
||||||
|
router.post('/check', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const serviceTargets = {
|
||||||
|
USER_AUTH_URL: process.env.USER_AUTH_URL || 'http://localhost:8011',
|
||||||
|
TEMPLATE_MANAGER_URL: process.env.TEMPLATE_MANAGER_URL || 'http://localhost:8009',
|
||||||
|
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001',
|
||||||
|
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://localhost:8002',
|
||||||
|
ARCHITECTURE_DESIGNER_URL: process.env.ARCHITECTURE_DESIGNER_URL || 'http://localhost:8003',
|
||||||
|
CODE_GENERATOR_URL: process.env.CODE_GENERATOR_URL || 'http://localhost:8004',
|
||||||
|
TEST_GENERATOR_URL: process.env.TEST_GENERATOR_URL || 'http://localhost:8005',
|
||||||
|
DEPLOYMENT_MANAGER_URL: process.env.DEPLOYMENT_MANAGER_URL || 'http://localhost:8006',
|
||||||
|
DASHBOARD_URL: process.env.DASHBOARD_URL || 'http://localhost:8008',
|
||||||
|
SELF_IMPROVING_GENERATOR_URL: process.env.SELF_IMPROVING_GENERATOR_URL || 'http://localhost:8007',
|
||||||
|
};
|
||||||
|
|
||||||
|
await healthMonitor.checkAllServices(serviceTargets);
|
||||||
|
const status = healthMonitor.getServiceStatus();
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: 'Health check completed',
|
||||||
|
...status
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Health check failed',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = { router };
|
||||||
130
services/api-gateway/src/routes/serviceRouter.js
Normal file
130
services/api-gateway/src/routes/serviceRouter.js
Normal file
@ -0,0 +1,130 @@
|
|||||||
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||||
|
const { logProxyRequest, logProxyResponse, logProxyError } = require('../middleware/requestLogger');
|
||||||
|
|
||||||
|
// Create service proxy with enhanced logging and error handling
|
||||||
|
const createServiceProxy = (targetUrl, serviceName, options = {}) => {
|
||||||
|
return createProxyMiddleware({
|
||||||
|
target: targetUrl,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
timeout: parseInt(process.env.PROXY_TIMEOUT) || 30000,
|
||||||
|
proxyTimeout: parseInt(process.env.PROXY_TIMEOUT) || 30000,
|
||||||
|
pathRewrite: options.pathRewrite || {},
|
||||||
|
onProxyReq: logProxyRequest(serviceName, targetUrl),
|
||||||
|
onProxyRes: logProxyResponse(serviceName),
|
||||||
|
onError: (err, req, res) => {
|
||||||
|
logProxyError(serviceName)(err, req, res);
|
||||||
|
|
||||||
|
if (!res.headersSent) {
|
||||||
|
// Handle different types of proxy errors
|
||||||
|
if (err.code === 'ECONNREFUSED' || err.code === 'ENOTFOUND') {
|
||||||
|
return res.status(503).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Service temporarily unavailable',
|
||||||
|
service: serviceName,
|
||||||
|
error: 'The requested service is currently unavailable',
|
||||||
|
request_id: req.requestId,
|
||||||
|
retry_after: 30
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (err.code === 'ETIMEDOUT' || err.message.includes('timeout')) {
|
||||||
|
return res.status(504).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Service timeout',
|
||||||
|
service: serviceName,
|
||||||
|
error: 'The service took too long to respond',
|
||||||
|
request_id: req.requestId,
|
||||||
|
retry_after: 60
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic proxy error
|
||||||
|
res.status(502).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Bad Gateway',
|
||||||
|
service: serviceName,
|
||||||
|
error: 'Unable to connect to the service',
|
||||||
|
request_id: req.requestId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create auth-specific proxy (no authentication required for login/register)
|
||||||
|
const createAuthProxy = () => {
|
||||||
|
const targetUrl = process.env.USER_AUTH_URL || 'http://localhost:8011';
|
||||||
|
|
||||||
|
console.log(`🔧 [PROXY CREATION] Creating auth proxy targeting: ${targetUrl}`);
|
||||||
|
|
||||||
|
return createProxyMiddleware({
|
||||||
|
target: targetUrl,
|
||||||
|
changeOrigin: true,
|
||||||
|
ws: true,
|
||||||
|
timeout: 30000,
|
||||||
|
proxyTimeout: 30000,
|
||||||
|
// Don't parse body - let it pass through
|
||||||
|
parseReqBody: false,
|
||||||
|
// Strip /api/auth prefix before forwarding to service
|
||||||
|
pathRewrite: {
|
||||||
|
'^/api/auth': ''
|
||||||
|
},
|
||||||
|
onProxyReq: (proxyReq, req, res) => {
|
||||||
|
console.log(`🔥 [PROXY MIDDLEWARE TRIGGERED!] This means the proxy is working!`);
|
||||||
|
|
||||||
|
// Calculate the rewritten path
|
||||||
|
const rewrittenPath = req.url.replace(/^\/api\/auth/, '');
|
||||||
|
const finalUrl = `${targetUrl}${rewrittenPath}`;
|
||||||
|
|
||||||
|
console.log(`🔐 [AUTH PROXY DEBUG]`);
|
||||||
|
console.log(` Original URL: ${req.originalUrl}`);
|
||||||
|
console.log(` Request URL: ${req.url}`);
|
||||||
|
console.log(` Target URL: ${targetUrl}`);
|
||||||
|
console.log(` Rewritten Path: ${rewrittenPath}`);
|
||||||
|
console.log(` FINAL URL HITTING: ${finalUrl}`);
|
||||||
|
console.log(` Method: ${req.method}`);
|
||||||
|
console.log(` Headers: ${JSON.stringify(req.headers, null, 2)}`);
|
||||||
|
|
||||||
|
// Add gateway headers
|
||||||
|
proxyReq.setHeader('X-Gateway-Request-ID', req.requestId);
|
||||||
|
proxyReq.setHeader('X-Gateway-Timestamp', new Date().toISOString());
|
||||||
|
proxyReq.setHeader('X-Forwarded-By', 'api-gateway');
|
||||||
|
proxyReq.setHeader('X-Forwarded-For', req.ip);
|
||||||
|
proxyReq.setHeader('X-Forwarded-Proto', req.protocol);
|
||||||
|
proxyReq.setHeader('X-Forwarded-Host', req.get('host'));
|
||||||
|
|
||||||
|
// Ensure content-type and content-length are preserved
|
||||||
|
if (req.headers['content-type']) {
|
||||||
|
proxyReq.setHeader('Content-Type', req.headers['content-type']);
|
||||||
|
}
|
||||||
|
if (req.headers['content-length']) {
|
||||||
|
proxyReq.setHeader('Content-Length', req.headers['content-length']);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
onProxyRes: (proxyRes, req, res) => {
|
||||||
|
console.log(`🔐 [AUTH RESPONSE] ${proxyRes.statusCode} for ${req.method} ${req.originalUrl}`);
|
||||||
|
|
||||||
|
// Add CORS headers
|
||||||
|
proxyRes.headers['Access-Control-Allow-Origin'] = req.headers.origin || '*';
|
||||||
|
proxyRes.headers['Access-Control-Allow-Credentials'] = 'true';
|
||||||
|
},
|
||||||
|
onError: (err, req, res) => {
|
||||||
|
console.error(`❌ [AUTH PROXY ERROR] ${req.method} ${req.originalUrl}:`, err.message);
|
||||||
|
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(502).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Authentication service unavailable',
|
||||||
|
error: 'Unable to connect to authentication service',
|
||||||
|
request_id: req.requestId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
module.exports = {
|
||||||
|
createServiceProxy,
|
||||||
|
createAuthProxy
|
||||||
|
};
|
||||||
123
services/api-gateway/src/routes/websocketRouter.js
Normal file
123
services/api-gateway/src/routes/websocketRouter.js
Normal file
@ -0,0 +1,123 @@
|
|||||||
|
const express = require('express');
|
||||||
|
const { verifyToken } = require('../middleware/authentication');
|
||||||
|
|
||||||
|
const router = express.Router();
|
||||||
|
|
||||||
|
// Get WebSocket connection info
|
||||||
|
router.get('/info', verifyToken, (req, res) => {
|
||||||
|
const io = global.io;
|
||||||
|
|
||||||
|
if (!io) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'WebSocket server not initialized'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
websocket: {
|
||||||
|
connected_clients: io.engine.clientsCount,
|
||||||
|
transport_types: ['websocket', 'polling'],
|
||||||
|
endpoint: '/socket.io/',
|
||||||
|
authentication_required: true
|
||||||
|
},
|
||||||
|
rooms: {
|
||||||
|
user_rooms: `user_${req.user.id || req.user.userId}`,
|
||||||
|
available_services: [
|
||||||
|
'user-auth', 'template-manager', 'requirement-processor',
|
||||||
|
'tech-stack-selector', 'architecture-designer', 'code-generator',
|
||||||
|
'test-generator', 'deployment-manager', 'dashboard', 'self-improving-generator'
|
||||||
|
]
|
||||||
|
}
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Broadcast message to specific service subscribers
|
||||||
|
router.post('/broadcast/service/:serviceName', verifyToken, (req, res) => {
|
||||||
|
const { serviceName } = req.params;
|
||||||
|
const { message, type = 'info' } = req.body;
|
||||||
|
const io = global.io;
|
||||||
|
|
||||||
|
if (!io) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'WebSocket server not initialized'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const serviceRoom = `service_${serviceName}`;
|
||||||
|
io.to(serviceRoom).emit('service_broadcast', {
|
||||||
|
service: serviceName,
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
from: req.user.email || req.user.id,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `Broadcast sent to ${serviceName} subscribers`,
|
||||||
|
room: serviceRoom
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Broadcast message to specific project
|
||||||
|
router.post('/broadcast/project/:projectId', verifyToken, (req, res) => {
|
||||||
|
const { projectId } = req.params;
|
||||||
|
const { message, type = 'info' } = req.body;
|
||||||
|
const io = global.io;
|
||||||
|
|
||||||
|
if (!io) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'WebSocket server not initialized'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const projectRoom = `project_${projectId}`;
|
||||||
|
io.to(projectRoom).emit('project_broadcast', {
|
||||||
|
projectId,
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
from: req.user.email || req.user.id,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `Broadcast sent to project ${projectId}`,
|
||||||
|
room: projectRoom
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Send notification to specific user
|
||||||
|
router.post('/notify/:userId', verifyToken, (req, res) => {
|
||||||
|
const { userId } = req.params;
|
||||||
|
const { message, type = 'info', title } = req.body;
|
||||||
|
const io = global.io;
|
||||||
|
|
||||||
|
if (!io) {
|
||||||
|
return res.status(500).json({
|
||||||
|
success: false,
|
||||||
|
message: 'WebSocket server not initialized'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const userRoom = `user_${userId}`;
|
||||||
|
io.to(userRoom).emit('notification', {
|
||||||
|
title,
|
||||||
|
message,
|
||||||
|
type,
|
||||||
|
from: req.user.email || req.user.id,
|
||||||
|
timestamp: new Date().toISOString()
|
||||||
|
});
|
||||||
|
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
message: `Notification sent to user ${userId}`,
|
||||||
|
room: userRoom
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = router;
|
||||||
@ -1,113 +1,531 @@
|
|||||||
const express = require('express');
|
const express = require('express');
|
||||||
|
const http = require('http');
|
||||||
|
const socketIo = require('socket.io');
|
||||||
const cors = require('cors');
|
const cors = require('cors');
|
||||||
const helmet = require('helmet');
|
const helmet = require('helmet');
|
||||||
const morgan = require('morgan');
|
const morgan = require('morgan');
|
||||||
const rateLimit = require('express-rate-limit');
|
const rateLimit = require('express-rate-limit');
|
||||||
const { createServer } = require('http');
|
const { createProxyMiddleware } = require('http-proxy-middleware');
|
||||||
const { Server } = require('socket.io');
|
const jwt = require('jsonwebtoken');
|
||||||
|
const axios = require('axios');
|
||||||
require('dotenv').config();
|
require('dotenv').config();
|
||||||
|
|
||||||
|
// Import middleware
|
||||||
|
const authMiddleware = require('./middleware/authentication');
|
||||||
|
const serviceHealthMiddleware = require('./middleware/serviceHealth');
|
||||||
|
const requestLogger = require('./middleware/requestLogger');
|
||||||
|
const websocketAuth = require('./middleware/webSocket');
|
||||||
|
|
||||||
|
// Import route handlers
|
||||||
|
const serviceRouter = require('./routes/serviceRouter');
|
||||||
|
const healthRouter = require('./routes/healthRouter');
|
||||||
|
const websocketRouter = require('./routes/websocketRouter');
|
||||||
|
|
||||||
const app = express();
|
const app = express();
|
||||||
const server = createServer(app);
|
const server = http.createServer(app);
|
||||||
const io = new Server(server, {
|
const PORT = process.env.PORT || 8000;
|
||||||
|
|
||||||
|
// Initialize Socket.IO with CORS
|
||||||
|
const io = socketIo(server, {
|
||||||
cors: {
|
cors: {
|
||||||
origin: "*",
|
origin: process.env.FRONTEND_URL || "*",
|
||||||
methods: ["GET", "POST"]
|
credentials: true,
|
||||||
|
methods: ['GET', 'POST']
|
||||||
|
},
|
||||||
|
transports: ['websocket', 'polling']
|
||||||
|
});
|
||||||
|
|
||||||
|
// Make io available globally for other modules
|
||||||
|
global.io = io;
|
||||||
|
|
||||||
|
// Service targets configuration
|
||||||
|
const serviceTargets = {
|
||||||
|
USER_AUTH_URL: process.env.USER_AUTH_URL || 'http://localhost:8011',
|
||||||
|
TEMPLATE_MANAGER_URL: process.env.TEMPLATE_MANAGER_URL || 'http://localhost:8009',
|
||||||
|
REQUIREMENT_PROCESSOR_URL: process.env.REQUIREMENT_PROCESSOR_URL || 'http://localhost:8001',
|
||||||
|
TECH_STACK_SELECTOR_URL: process.env.TECH_STACK_SELECTOR_URL || 'http://localhost:8002',
|
||||||
|
ARCHITECTURE_DESIGNER_URL: process.env.ARCHITECTURE_DESIGNER_URL || 'http://localhost:8003',
|
||||||
|
CODE_GENERATOR_URL: process.env.CODE_GENERATOR_URL || 'http://localhost:8004',
|
||||||
|
TEST_GENERATOR_URL: process.env.TEST_GENERATOR_URL || 'http://localhost:8005',
|
||||||
|
DEPLOYMENT_MANAGER_URL: process.env.DEPLOYMENT_MANAGER_URL || 'http://localhost:8006',
|
||||||
|
DASHBOARD_URL: process.env.DASHBOARD_URL || 'http://localhost:8008',
|
||||||
|
SELF_IMPROVING_GENERATOR_URL: process.env.SELF_IMPROVING_GENERATOR_URL || 'http://localhost:8007',
|
||||||
|
};
|
||||||
|
|
||||||
|
// ========================================
|
||||||
|
// MIDDLEWARE SETUP
|
||||||
|
// ========================================
|
||||||
|
|
||||||
|
// Security middleware
|
||||||
|
app.use(helmet({
|
||||||
|
contentSecurityPolicy: {
|
||||||
|
directives: {
|
||||||
|
defaultSrc: ["'self'"],
|
||||||
|
styleSrc: ["'self'", "'unsafe-inline'"],
|
||||||
|
scriptSrc: ["'self'"],
|
||||||
|
imgSrc: ["'self'", "data:", "https:"],
|
||||||
|
connectSrc: ["'self'", "ws:", "wss:"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
// CORS configuration
|
||||||
|
app.use(cors({
|
||||||
|
origin: process.env.FRONTEND_URL || "*",
|
||||||
|
credentials: true,
|
||||||
|
optionsSuccessStatus: 200,
|
||||||
|
methods: ['GET', 'POST', 'PUT', 'DELETE', 'OPTIONS'],
|
||||||
|
allowedHeaders: [
|
||||||
|
'Origin', 'X-Requested-With', 'Content-Type', 'Accept', 'Authorization',
|
||||||
|
'X-Gateway-Request-ID', 'X-Gateway-Timestamp', 'X-Forwarded-By',
|
||||||
|
'X-Forwarded-For', 'X-Forwarded-Proto', 'X-Forwarded-Host',
|
||||||
|
'X-Session-Token', 'X-Platform', 'X-App-Version'
|
||||||
|
],
|
||||||
|
exposedHeaders: [
|
||||||
|
'X-Gateway-Request-ID', 'X-Gateway-Timestamp', 'X-Forwarded-By',
|
||||||
|
'X-Forwarded-For', 'X-Forwarded-Proto', 'X-Forwarded-Host'
|
||||||
|
]
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Request parsing middleware - only for non-proxy routes
|
||||||
|
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('/health', express.json({ limit: '10mb' }));
|
||||||
|
|
||||||
|
// Trust proxy for accurate IP addresses
|
||||||
|
app.set('trust proxy', 1);
|
||||||
|
|
||||||
|
// Request ID middleware for tracing
|
||||||
|
app.use((req, res, next) => {
|
||||||
|
req.requestId = `gw-${Date.now()}-${Math.random().toString(36).substr(2, 9)}`;
|
||||||
|
res.setHeader('X-Request-ID', req.requestId);
|
||||||
|
next();
|
||||||
|
});
|
||||||
|
|
||||||
|
app.use(morgan(process.env.NODE_ENV === 'production' ? 'combined' : 'dev'));
|
||||||
|
|
||||||
|
// Custom request logger for service tracking
|
||||||
|
app.use(requestLogger.logRequest);
|
||||||
|
|
||||||
|
// Rate limiting configuration
|
||||||
|
const createServiceLimiter = (maxRequests = 1000) => rateLimit({
|
||||||
|
windowMs: parseInt(process.env.RATE_LIMIT_WINDOW_MS) || 15 * 60 * 1000,
|
||||||
|
max: maxRequests,
|
||||||
|
message: {
|
||||||
|
success: false,
|
||||||
|
message: 'Too many requests, please try again later.',
|
||||||
|
retry_after: 900
|
||||||
|
},
|
||||||
|
standardHeaders: true,
|
||||||
|
legacyHeaders: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// Health check endpoint (before rate limiting and authentication)
|
||||||
|
app.get('/health', (req, res) => {
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
service: 'api-gateway',
|
||||||
|
status: 'healthy',
|
||||||
|
timestamp: new Date().toISOString(),
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
environment: process.env.NODE_ENV || 'development',
|
||||||
|
uptime: process.uptime(),
|
||||||
|
services: {
|
||||||
|
user_auth: process.env.USER_AUTH_URL ? 'configured' : 'not configured',
|
||||||
|
template_manager: process.env.TEMPLATE_MANAGER_URL ? 'configured' : 'not configured',
|
||||||
|
requirement_processor: process.env.REQUIREMENT_PROCESSOR_URL ? 'configured' : 'not configured',
|
||||||
|
tech_stack_selector: process.env.TECH_STACK_SELECTOR_URL ? 'configured' : 'not configured',
|
||||||
|
architecture_designer: process.env.ARCHITECTURE_DESIGNER_URL ? 'configured' : 'not configured',
|
||||||
|
code_generator: process.env.CODE_GENERATOR_URL ? 'configured' : 'not configured',
|
||||||
|
test_generator: process.env.TEST_GENERATOR_URL ? 'configured' : 'not configured',
|
||||||
|
deployment_manager: process.env.DEPLOYMENT_MANAGER_URL ? 'configured' : 'not configured',
|
||||||
|
dashboard: process.env.DASHBOARD_URL ? 'configured' : 'not configured',
|
||||||
|
self_improving_generator: process.env.SELF_IMPROVING_GENERATOR_URL ? 'configured' : 'not configured'
|
||||||
|
},
|
||||||
|
websocket: 'enabled'
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Service health monitoring routes
|
||||||
|
app.use('/health', healthRouter.router);
|
||||||
|
|
||||||
|
// WebSocket connection handling
|
||||||
|
const websocketHandlers = websocketAuth(io);
|
||||||
|
|
||||||
|
// Auth Service - Fixed proxy with proper connection handling
|
||||||
|
console.log('🔧 Registering /api/auth proxy route...');
|
||||||
|
app.use('/api/auth', (req, res, next) => {
|
||||||
|
console.log(`🔥 [AUTH PROXY] ${req.method} ${req.originalUrl} → http://localhost:8011${req.originalUrl}`);
|
||||||
|
|
||||||
|
// Set response timeout to prevent hanging
|
||||||
|
res.setTimeout(15000, () => {
|
||||||
|
console.error('❌ [AUTH PROXY] Response timeout');
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(504).json({ error: 'Gateway timeout', service: 'user-auth' });
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
const PORT = process.env.PORT || 8000;
|
const options = {
|
||||||
|
method: req.method,
|
||||||
// Middleware
|
url: `http://localhost:8011${req.originalUrl}`,
|
||||||
app.use(helmet());
|
headers: {
|
||||||
app.use(cors());
|
'Content-Type': 'application/json',
|
||||||
app.use(morgan('combined'));
|
'User-Agent': 'API-Gateway/1.0',
|
||||||
app.use(express.json({ limit: '10mb' }));
|
'Connection': 'keep-alive'
|
||||||
app.use(express.urlencoded({ extended: true }));
|
|
||||||
|
|
||||||
// Rate limiting
|
|
||||||
const limiter = rateLimit({
|
|
||||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
|
||||||
max: 1000, // limit each IP to 1000 requests per windowMs
|
|
||||||
message: 'Too many requests from this IP, please try again later.',
|
|
||||||
standardHeaders: true,
|
|
||||||
legacyHeaders: false,
|
|
||||||
});
|
|
||||||
app.use('/api/', limiter);
|
|
||||||
|
|
||||||
// Health check endpoint
|
|
||||||
app.get('/health', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
status: 'healthy',
|
|
||||||
service: 'api-gateway',
|
|
||||||
timestamp: new Date().toISOString(),
|
|
||||||
version: '1.0.0',
|
|
||||||
uptime: process.uptime(),
|
|
||||||
memory: process.memoryUsage()
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
// Basic routes
|
|
||||||
app.get('/api/v1/status', (req, res) => {
|
|
||||||
res.json({
|
|
||||||
message: 'API Gateway is running',
|
|
||||||
environment: process.env.NODE_ENV || 'development',
|
|
||||||
services: {
|
|
||||||
'requirement-processor': 'pending',
|
|
||||||
'tech-stack-selector': 'pending',
|
|
||||||
'architecture-designer': 'pending',
|
|
||||||
'code-generator': 'pending',
|
|
||||||
'test-generator': 'pending',
|
|
||||||
'deployment-manager': 'pending'
|
|
||||||
},
|
},
|
||||||
timestamp: new Date().toISOString()
|
timeout: 8000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
maxRedirects: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always include request body for POST/PUT/PATCH requests
|
||||||
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
||||||
|
options.data = req.body || {};
|
||||||
|
console.log(`📦 [AUTH PROXY] Request body:`, JSON.stringify(req.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
axios(options)
|
||||||
|
.then(response => {
|
||||||
|
console.log(`✅ [AUTH PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(response.status).json(response.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`❌ [AUTH 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: 'Auth service unavailable',
|
||||||
|
message: error.code || error.message,
|
||||||
|
service: 'user-auth'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// WebSocket connection handling
|
// WebSocket API routes for managing connections
|
||||||
io.on('connection', (socket) => {
|
app.use('/api/websocket', websocketRouter);
|
||||||
console.log('Client connected:', socket.id);
|
|
||||||
|
|
||||||
socket.on('disconnect', () => {
|
// Apply rate limiting to other API routes
|
||||||
console.log('Client disconnected:', socket.id);
|
app.use('/api', createServiceLimiter(1000));
|
||||||
|
|
||||||
|
// Template Manager Service - Direct HTTP forwarding
|
||||||
|
console.log('🔧 Registering /api/templates proxy route...');
|
||||||
|
app.use('/api/templates',
|
||||||
|
createServiceLimiter(200),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
(req, res, next) => {
|
||||||
|
console.log(`🔥 [TEMPLATE PROXY] ${req.method} ${req.originalUrl} → http://localhost:8009${req.originalUrl}`);
|
||||||
|
|
||||||
|
// Set response timeout to prevent hanging
|
||||||
|
res.setTimeout(15000, () => {
|
||||||
|
console.error('❌ [TEMPLATE PROXY] Response timeout');
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(504).json({ error: 'Gateway timeout', service: 'template-manager' });
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Send initial connection confirmation
|
const options = {
|
||||||
socket.emit('connected', {
|
method: req.method,
|
||||||
message: 'Connected to API Gateway',
|
url: `http://localhost:8009${req.originalUrl}`,
|
||||||
timestamp: new Date().toISOString()
|
headers: {
|
||||||
|
'Content-Type': 'application/json',
|
||||||
|
'User-Agent': 'API-Gateway/1.0',
|
||||||
|
'Connection': 'keep-alive',
|
||||||
|
// Forward user context from auth middleware
|
||||||
|
'X-User-ID': req.user?.id || req.user?.userId,
|
||||||
|
'X-User-Role': req.user?.role,
|
||||||
|
'Authorization': req.headers.authorization
|
||||||
|
},
|
||||||
|
timeout: 8000,
|
||||||
|
validateStatus: () => true,
|
||||||
|
maxRedirects: 0
|
||||||
|
};
|
||||||
|
|
||||||
|
// Always include request body for POST/PUT/PATCH requests
|
||||||
|
if (req.method === 'POST' || req.method === 'PUT' || req.method === 'PATCH') {
|
||||||
|
options.data = req.body || {};
|
||||||
|
console.log(`📦 [TEMPLATE PROXY] Request body:`, JSON.stringify(req.body));
|
||||||
|
}
|
||||||
|
|
||||||
|
axios(options)
|
||||||
|
.then(response => {
|
||||||
|
console.log(`✅ [TEMPLATE PROXY] Response: ${response.status} for ${req.method} ${req.originalUrl}`);
|
||||||
|
if (!res.headersSent) {
|
||||||
|
res.status(response.status).json(response.data);
|
||||||
|
}
|
||||||
|
})
|
||||||
|
.catch(error => {
|
||||||
|
console.error(`❌ [TEMPLATE 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 service unavailable',
|
||||||
|
message: error.code || error.message,
|
||||||
|
service: 'template-manager'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
);
|
||||||
|
|
||||||
|
// Requirement Processor Service
|
||||||
|
app.use('/api/requirements',
|
||||||
|
createServiceLimiter(300),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.REQUIREMENT_PROCESSOR_URL, 'requirement-processor')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Tech Stack Selector Service
|
||||||
|
app.use('/api/tech-stack',
|
||||||
|
createServiceLimiter(200),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.TECH_STACK_SELECTOR_URL, 'tech-stack-selector')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Architecture Designer Service
|
||||||
|
app.use('/api/architecture',
|
||||||
|
createServiceLimiter(150),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.ARCHITECTURE_DESIGNER_URL, 'architecture-designer')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Code Generator Service
|
||||||
|
app.use('/api/codegen',
|
||||||
|
createServiceLimiter(100),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.CODE_GENERATOR_URL, 'code-generator')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Test Generator Service
|
||||||
|
app.use('/api/tests',
|
||||||
|
createServiceLimiter(150),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.TEST_GENERATOR_URL, 'test-generator')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Deployment Manager Service
|
||||||
|
app.use('/api/deploy',
|
||||||
|
createServiceLimiter(100),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.DEPLOYMENT_MANAGER_URL, 'deployment-manager')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Dashboard Service
|
||||||
|
app.use('/api/dashboard',
|
||||||
|
createServiceLimiter(300),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.DASHBOARD_URL, 'dashboard')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Self-Improving Generator Service
|
||||||
|
app.use('/api/self-improving',
|
||||||
|
createServiceLimiter(50),
|
||||||
|
authMiddleware.verifyToken,
|
||||||
|
authMiddleware.forwardUserContext,
|
||||||
|
serviceRouter.createServiceProxy(serviceTargets.SELF_IMPROVING_GENERATOR_URL, 'self-improving-generator')
|
||||||
|
);
|
||||||
|
|
||||||
|
// Gateway management endpoints
|
||||||
|
app.get('/api/gateway/info', authMiddleware.verifyToken, (req, res) => {
|
||||||
|
res.json({
|
||||||
|
success: true,
|
||||||
|
gateway: {
|
||||||
|
name: 'CodeNuk API Gateway',
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
environment: process.env.NODE_ENV || 'development',
|
||||||
|
uptime: process.uptime()
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
total_services: Object.keys(serviceTargets).length,
|
||||||
|
operational_services: Object.keys(serviceTargets).length,
|
||||||
|
service_urls: serviceTargets
|
||||||
|
},
|
||||||
|
features: {
|
||||||
|
websocket_enabled: true,
|
||||||
|
authentication: true,
|
||||||
|
rate_limiting: true,
|
||||||
|
health_monitoring: true,
|
||||||
|
request_logging: true,
|
||||||
|
cors_enabled: true
|
||||||
|
},
|
||||||
|
websocket: {
|
||||||
|
connected_clients: io.engine.clientsCount,
|
||||||
|
transport_types: ['websocket', 'polling']
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// Error handling middleware
|
// Service status endpoint
|
||||||
app.use((err, req, res, next) => {
|
app.get('/api/gateway/services', authMiddleware.verifyToken, serviceHealthMiddleware.getServiceStatus);
|
||||||
console.error('Error:', err.stack);
|
|
||||||
res.status(500).json({
|
// Root endpoint
|
||||||
error: 'Internal server error',
|
app.get('/', (req, res) => {
|
||||||
message: process.env.NODE_ENV === 'development' ? err.message : 'Something went wrong!',
|
res.json({
|
||||||
timestamp: new Date().toISOString()
|
success: true,
|
||||||
|
message: 'CodeNuk API Gateway',
|
||||||
|
version: process.env.npm_package_version || '1.0.0',
|
||||||
|
description: 'Central gateway for all CodeNuk microservices',
|
||||||
|
documentation: {
|
||||||
|
health: '/health',
|
||||||
|
gateway_info: '/api/gateway/info',
|
||||||
|
service_status: '/api/gateway/services'
|
||||||
|
},
|
||||||
|
services: {
|
||||||
|
auth: '/api/auth',
|
||||||
|
templates: '/api/templates',
|
||||||
|
requirements: '/api/requirements',
|
||||||
|
tech_stack: '/api/tech-stack',
|
||||||
|
architecture: '/api/architecture',
|
||||||
|
codegen: '/api/codegen',
|
||||||
|
tests: '/api/tests',
|
||||||
|
deploy: '/api/deploy',
|
||||||
|
dashboard: '/api/dashboard',
|
||||||
|
self_improving: '/api/self-improving'
|
||||||
|
},
|
||||||
|
websocket: {
|
||||||
|
endpoint: '/socket.io/',
|
||||||
|
authentication_required: true
|
||||||
|
}
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
// 404 handler
|
// 404 handler
|
||||||
app.use('*', (req, res) => {
|
app.use('*', (req, res) => {
|
||||||
res.status(404).json({
|
res.status(404).json({
|
||||||
error: 'Route not found',
|
success: false,
|
||||||
path: req.originalUrl,
|
message: 'Endpoint not found',
|
||||||
timestamp: new Date().toISOString()
|
available_services: {
|
||||||
|
auth: '/api/auth',
|
||||||
|
templates: '/api/templates',
|
||||||
|
requirements: '/api/requirements',
|
||||||
|
tech_stack: '/api/tech-stack',
|
||||||
|
architecture: '/api/architecture',
|
||||||
|
codegen: '/api/codegen',
|
||||||
|
tests: '/api/tests',
|
||||||
|
deploy: '/api/deploy',
|
||||||
|
dashboard: '/api/dashboard',
|
||||||
|
self_improving: '/api/self-improving'
|
||||||
|
},
|
||||||
|
documentation: '/api/gateway/info'
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Global error handler
|
||||||
|
app.use((error, req, res, next) => {
|
||||||
|
console.error(`[${req.requestId}] Gateway Error:`, error);
|
||||||
|
|
||||||
|
// Handle proxy errors
|
||||||
|
if (error.code === 'ECONNREFUSED' || error.code === 'ENOTFOUND') {
|
||||||
|
return res.status(503).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Service temporarily unavailable',
|
||||||
|
error: 'The requested service is currently unavailable',
|
||||||
|
request_id: req.requestId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle timeout errors
|
||||||
|
if (error.code === 'ETIMEDOUT' || error.message.includes('timeout')) {
|
||||||
|
return res.status(504).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Service timeout',
|
||||||
|
error: 'The service took too long to respond',
|
||||||
|
request_id: req.requestId
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Handle authentication errors
|
||||||
|
if (error.name === 'JsonWebTokenError' || error.name === 'TokenExpiredError') {
|
||||||
|
return res.status(401).json({
|
||||||
|
success: false,
|
||||||
|
message: 'Authentication failed',
|
||||||
|
error: error.message
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Generic error handler
|
||||||
|
res.status(error.status || 500).json({
|
||||||
|
success: false,
|
||||||
|
message: error.message || 'Internal gateway error',
|
||||||
|
error: process.env.NODE_ENV === 'development' ? {
|
||||||
|
stack: error.stack,
|
||||||
|
name: error.name
|
||||||
|
} : undefined,
|
||||||
|
request_id: req.requestId
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start server and initialize health monitoring
|
||||||
|
const startServer = async () => {
|
||||||
|
try {
|
||||||
|
console.log('🚀 Starting CodeNuk API Gateway...');
|
||||||
|
|
||||||
|
// Initialize service health monitoring
|
||||||
|
await serviceHealthMiddleware.initializeHealthMonitoring();
|
||||||
|
|
||||||
|
server.listen(PORT, '0.0.0.0', () => {
|
||||||
|
console.log(`✅ API Gateway running on port ${PORT}`);
|
||||||
|
console.log(`🌍 Environment: ${process.env.NODE_ENV || 'development'}`);
|
||||||
|
console.log(`📋 Health check: http://localhost:${PORT}/health`);
|
||||||
|
console.log(`📖 Gateway info: http://localhost:${PORT}/api/gateway/info`);
|
||||||
|
console.log(`🔗 WebSocket enabled on: ws://localhost:${PORT}`);
|
||||||
|
|
||||||
|
// Log service configuration
|
||||||
|
console.log('⚙️ Configured Services:');
|
||||||
|
Object.entries(serviceTargets).forEach(([name, url]) => {
|
||||||
|
console.log(` - ${name}: ${url}`);
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('🔧 Features:');
|
||||||
|
console.log(` - Rate Limiting: Enabled`);
|
||||||
|
console.log(` - Authentication: JWT with Auth Service`);
|
||||||
|
console.log(` - WebSocket: Real-time notifications`);
|
||||||
|
console.log(` - Health Monitoring: All services`);
|
||||||
|
console.log(` - Request Logging: Enabled`);
|
||||||
|
});
|
||||||
|
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Failed to start API Gateway:', error);
|
||||||
|
process.exit(1);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
// Graceful shutdown
|
// Graceful shutdown
|
||||||
process.on('SIGTERM', () => {
|
process.on('SIGTERM', () => {
|
||||||
console.log('SIGTERM received, shutting down gracefully');
|
console.log('📴 SIGTERM received, shutting down gracefully...');
|
||||||
server.close(() => {
|
server.close(() => {
|
||||||
console.log('Process terminated');
|
console.log('✅ API Gateway shut down successfully');
|
||||||
|
process.exit(0);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
server.listen(PORT, '0.0.0.0', () => {
|
process.on('SIGINT', () => {
|
||||||
console.log(`🚀 API Gateway running on port ${PORT}`);
|
console.log('📴 SIGINT received, shutting down gracefully...');
|
||||||
console.log(`📊 Health check: http://localhost:${PORT}/health`);
|
server.close(() => {
|
||||||
console.log(`📡 WebSocket server started`);
|
console.log('✅ API Gateway shut down successfully');
|
||||||
|
process.exit(0);
|
||||||
});
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
// Start the server
|
||||||
|
startServer();
|
||||||
|
|
||||||
|
module.exports = app;
|
||||||
@ -283,6 +283,13 @@ class CustomTemplate {
|
|||||||
admin_reviewed_by
|
admin_reviewed_by
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Maintain the legacy boolean flag alongside the status for easier filtering
|
||||||
|
if (status === 'approved') {
|
||||||
|
updates.approved = true;
|
||||||
|
} else if (status === 'rejected' || status === 'duplicate') {
|
||||||
|
updates.approved = false;
|
||||||
|
}
|
||||||
|
|
||||||
if (canonical_template_id) {
|
if (canonical_template_id) {
|
||||||
updates.canonical_template_id = canonical_template_id;
|
updates.canonical_template_id = canonical_template_id;
|
||||||
}
|
}
|
||||||
|
|||||||
@ -485,6 +485,44 @@ router.post('/templates/:id/review', async (req, res) => {
|
|||||||
|
|
||||||
console.log(`🔍 Admin: Reviewing template ${id} with status: ${status}`);
|
console.log(`🔍 Admin: Reviewing template ${id} with status: ${status}`);
|
||||||
|
|
||||||
|
// If approving, also create a main template entry and link it, atomically
|
||||||
|
if (status === 'approved') {
|
||||||
|
const existingCustom = await CustomTemplate.getById(id);
|
||||||
|
if (!existingCustom) {
|
||||||
|
return res.status(404).json({
|
||||||
|
success: false,
|
||||||
|
error: 'Template not found',
|
||||||
|
message: 'The specified template does not exist'
|
||||||
|
});
|
||||||
|
}
|
||||||
|
// Create in main templates
|
||||||
|
const Template = require('../models/template');
|
||||||
|
const payload = {
|
||||||
|
type: existingCustom.type,
|
||||||
|
title: existingCustom.title,
|
||||||
|
description: existingCustom.description,
|
||||||
|
icon: existingCustom.icon,
|
||||||
|
category: existingCustom.category,
|
||||||
|
gradient: existingCustom.gradient,
|
||||||
|
border: existingCustom.border,
|
||||||
|
text: existingCustom.text,
|
||||||
|
subtext: existingCustom.subtext,
|
||||||
|
};
|
||||||
|
const created = await Template.create(payload);
|
||||||
|
// Update custom template flags and link canonical_template_id
|
||||||
|
const updatedCustom = await CustomTemplate.reviewTemplate(id, {
|
||||||
|
status: 'approved',
|
||||||
|
admin_notes,
|
||||||
|
canonical_template_id: created.id,
|
||||||
|
admin_reviewed_by: req.user.username || req.user.email
|
||||||
|
});
|
||||||
|
return res.json({
|
||||||
|
success: true,
|
||||||
|
data: { custom_template: updatedCustom, template: created },
|
||||||
|
message: `Template '${created.title}' created and custom template approved`
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
const template = await CustomTemplate.reviewTemplate(id, {
|
const template = await CustomTemplate.reviewTemplate(id, {
|
||||||
status,
|
status,
|
||||||
admin_notes,
|
admin_notes,
|
||||||
|
|||||||
@ -738,6 +738,27 @@ router.post('/', async (req, res) => {
|
|||||||
|
|
||||||
const template = await Template.create(templateData);
|
const template = await Template.create(templateData);
|
||||||
|
|
||||||
|
// Link back to custom_templates when approving from a custom
|
||||||
|
if (templateData.approved_from_custom) {
|
||||||
|
try {
|
||||||
|
const customId = templateData.approved_from_custom;
|
||||||
|
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
if (uuidV4Regex.test(customId)) {
|
||||||
|
await CustomTemplate.update(customId, {
|
||||||
|
approved: true,
|
||||||
|
status: 'approved',
|
||||||
|
canonical_template_id: template.id,
|
||||||
|
admin_reviewed_at: new Date(),
|
||||||
|
admin_reviewed_by: 'system_auto'
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
console.warn('[POST /api/templates] approved_from_custom is not a valid UUID v4');
|
||||||
|
}
|
||||||
|
} catch (linkErr) {
|
||||||
|
console.error('⚠️ Failed to set approved=true on custom_templates:', linkErr.message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
res.status(201).json({
|
res.status(201).json({
|
||||||
success: true,
|
success: true,
|
||||||
data: template,
|
data: template,
|
||||||
@ -763,6 +784,59 @@ router.post('/', async (req, res) => {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// POST /api/templates/approve-custom - Create main template and approve a custom template in one atomic flow
|
||||||
|
router.post('/approve-custom', async (req, res) => {
|
||||||
|
try {
|
||||||
|
const { custom_template_id, template } = req.body || {};
|
||||||
|
|
||||||
|
const customId = custom_template_id || req.body?.customTemplateId || req.body?.id;
|
||||||
|
const uuidV4Regex = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i;
|
||||||
|
if (!customId || !uuidV4Regex.test(customId)) {
|
||||||
|
return res.status(400).json({ success: false, error: 'Invalid custom_template_id', message: 'Provide a valid UUID v4 for custom_template_id' });
|
||||||
|
}
|
||||||
|
|
||||||
|
// Load custom template to mirror missing fields if needed
|
||||||
|
const existingCustom = await CustomTemplate.getById(customId);
|
||||||
|
if (!existingCustom) {
|
||||||
|
return res.status(404).json({ success: false, error: 'Custom template not found', message: `No custom template with id ${customId}` });
|
||||||
|
}
|
||||||
|
|
||||||
|
const payload = {
|
||||||
|
type: template?.type || existingCustom.type,
|
||||||
|
title: template?.title || existingCustom.title,
|
||||||
|
description: template?.description ?? existingCustom.description,
|
||||||
|
icon: template?.icon ?? existingCustom.icon,
|
||||||
|
category: template?.category || existingCustom.category,
|
||||||
|
gradient: template?.gradient ?? existingCustom.gradient,
|
||||||
|
border: template?.border ?? existingCustom.border,
|
||||||
|
text: template?.text ?? existingCustom.text,
|
||||||
|
subtext: template?.subtext ?? existingCustom.subtext,
|
||||||
|
approved_from_custom: customId
|
||||||
|
};
|
||||||
|
|
||||||
|
// Create in main templates
|
||||||
|
const created = await Template.create(payload);
|
||||||
|
|
||||||
|
// Mark custom template as approved and link canonical_template_id
|
||||||
|
await CustomTemplate.update(customId, {
|
||||||
|
approved: true,
|
||||||
|
status: 'approved',
|
||||||
|
canonical_template_id: created.id,
|
||||||
|
admin_reviewed_at: new Date(),
|
||||||
|
admin_reviewed_by: (req.user && (req.user.username || req.user.email)) || 'admin'
|
||||||
|
});
|
||||||
|
|
||||||
|
return res.status(201).json({
|
||||||
|
success: true,
|
||||||
|
data: { template: created, custom_template_id: customId },
|
||||||
|
message: `Template '${created.title}' created and custom template approved`
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
console.error('❌ Error approving custom template:', error.message);
|
||||||
|
return res.status(500).json({ success: false, error: 'Failed to approve custom template', message: error.message });
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
// PUT /api/templates/:id - Update template or custom template based on isCustom flag
|
// PUT /api/templates/:id - Update template or custom template based on isCustom flag
|
||||||
router.put('/:id', async (req, res) => {
|
router.put('/:id', async (req, res) => {
|
||||||
try {
|
try {
|
||||||
|
|||||||
@ -182,6 +182,42 @@ const TEMPLATE_DATA = {
|
|||||||
display_order: 2
|
display_order: 2
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'ai_chatbot_platform',
|
||||||
|
title: 'AI Chatbot Platform',
|
||||||
|
description: 'Deploy multi-channel AI chatbots with analytics and workflows',
|
||||||
|
icon: '🤖',
|
||||||
|
gradient: 'from-teal-50 to-teal-100',
|
||||||
|
border: 'border-teal-200',
|
||||||
|
text: 'text-teal-900',
|
||||||
|
subtext: 'text-teal-700',
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
feature_id: 'nlp_engine',
|
||||||
|
name: 'NLP Engine',
|
||||||
|
description: 'Natural language processing for intent and entity extraction',
|
||||||
|
feature_type: 'essential',
|
||||||
|
complexity: 'high',
|
||||||
|
display_order: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feature_id: 'channel_integrations',
|
||||||
|
name: 'Channel Integrations',
|
||||||
|
description: 'Integrate with web, WhatsApp, and Slack',
|
||||||
|
feature_type: 'essential',
|
||||||
|
complexity: 'medium',
|
||||||
|
display_order: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feature_id: 'conversation_analytics',
|
||||||
|
name: 'Conversation Analytics',
|
||||||
|
description: 'Track KPIs: containment rate, CSAT, and response time',
|
||||||
|
feature_type: 'suggested',
|
||||||
|
complexity: 'medium',
|
||||||
|
display_order: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
'Custom & Others': [
|
'Custom & Others': [
|
||||||
@ -195,6 +231,42 @@ const TEMPLATE_DATA = {
|
|||||||
text: 'text-gray-900',
|
text: 'text-gray-900',
|
||||||
subtext: 'text-gray-700',
|
subtext: 'text-gray-700',
|
||||||
features: []
|
features: []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
type: 'project_management_tool',
|
||||||
|
title: 'Project Management Tool',
|
||||||
|
description: 'Tasks, Kanban boards, sprints, and team collaboration',
|
||||||
|
icon: '🗂️',
|
||||||
|
gradient: 'from-amber-50 to-amber-100',
|
||||||
|
border: 'border-amber-200',
|
||||||
|
text: 'text-amber-900',
|
||||||
|
subtext: 'text-amber-700',
|
||||||
|
features: [
|
||||||
|
{
|
||||||
|
feature_id: 'kanban_boards',
|
||||||
|
name: 'Kanban Boards',
|
||||||
|
description: 'Organize tasks into customizable columns',
|
||||||
|
feature_type: 'essential',
|
||||||
|
complexity: 'medium',
|
||||||
|
display_order: 1
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feature_id: 'sprint_planning',
|
||||||
|
name: 'Sprint Planning',
|
||||||
|
description: 'Plan sprints, track velocity, and manage backlogs',
|
||||||
|
feature_type: 'essential',
|
||||||
|
complexity: 'high',
|
||||||
|
display_order: 2
|
||||||
|
},
|
||||||
|
{
|
||||||
|
feature_id: 'collaboration_tools',
|
||||||
|
name: 'Collaboration Tools',
|
||||||
|
description: 'Comments, mentions, and notifications',
|
||||||
|
feature_type: 'suggested',
|
||||||
|
complexity: 'medium',
|
||||||
|
display_order: 3
|
||||||
|
}
|
||||||
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
};
|
};
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user