Api docs (SWAGGER, REDOC) available
This commit is contained in:
5
.gitignore
vendored
5
.gitignore
vendored
@@ -24,4 +24,7 @@ build/
|
|||||||
**/.python_packages/
|
**/.python_packages/
|
||||||
**/.pytest_cache/
|
**/.pytest_cache/
|
||||||
**/.ruff_cache/
|
**/.ruff_cache/
|
||||||
.history/
|
.history/
|
||||||
|
|
||||||
|
#scripts
|
||||||
|
scripts/
|
||||||
@@ -100,6 +100,18 @@ services:
|
|||||||
ports:
|
ports:
|
||||||
- "${PAYMENTS_PORT:-8005}:8000"
|
- "${PAYMENTS_PORT:-8005}:8000"
|
||||||
command: ./docker-entrypoint.sh
|
command: ./docker-entrypoint.sh
|
||||||
|
docs:
|
||||||
|
build:
|
||||||
|
context: ./services/docs
|
||||||
|
container_name: marriage_docs
|
||||||
|
depends_on:
|
||||||
|
- auth
|
||||||
|
- profiles
|
||||||
|
- match
|
||||||
|
- chat
|
||||||
|
- payments
|
||||||
|
ports:
|
||||||
|
- "8090:8000"
|
||||||
|
command: uvicorn main:app --host 0.0.0.0 --port 8000 --reload
|
||||||
volumes:
|
volumes:
|
||||||
pgdata:
|
pgdata:
|
||||||
|
|||||||
@@ -2,49 +2,106 @@ server {
|
|||||||
listen 80;
|
listen 80;
|
||||||
server_name _;
|
server_name _;
|
||||||
|
|
||||||
|
# Docker DNS
|
||||||
|
resolver 127.0.0.11 ipv6=off valid=10s;
|
||||||
|
|
||||||
# Health of gateway itself
|
# Health of gateway itself
|
||||||
location = /health {
|
location = /health {
|
||||||
default_type application/json;
|
default_type application/json;
|
||||||
return 200 '{"status":"ok","gateway":"nginx"}';
|
return 200 '{"status":"ok","gateway":"nginx"}';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
# ===== Unified API Docs (docs aggregator) =====
|
||||||
|
location = /docs {
|
||||||
|
proxy_pass http://marriage_docs:8000/docs;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /redoc {
|
||||||
|
proxy_pass http://marriage_docs:8000/redoc;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /openapi.json {
|
||||||
|
proxy_pass http://marriage_docs:8000/openapi.json;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /refresh {
|
||||||
|
proxy_pass http://marriage_docs:8000/refresh;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
location = /_health {
|
||||||
|
proxy_pass http://marriage_docs:8000/_health;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
|
# ===== Microservices (prefix strip) =====
|
||||||
location /auth/ {
|
location /auth/ {
|
||||||
proxy_pass http://auth:8000/;
|
rewrite ^/auth/(.*)$ /$1 break;
|
||||||
proxy_set_header Authorization $http_authorization;
|
proxy_pass http://marriage_auth:8000;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
|
||||||
proxy_set_header Host $host;
|
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Authorization $http_authorization;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /profiles/ {
|
location /profiles/ {
|
||||||
proxy_pass http://profiles:8000/;
|
rewrite ^/profiles/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://marriage_profiles:8000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /match/ {
|
location /match/ {
|
||||||
proxy_pass http://match:8000/;
|
rewrite ^/match/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://marriage_match:8000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /chat/ {
|
location /chat/ {
|
||||||
proxy_pass http://chat:8000/;
|
rewrite ^/chat/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://marriage_chat:8000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
proxy_set_header X-Forwarded-Proto $scheme;
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
}
|
}
|
||||||
|
|
||||||
location /payments/ {
|
location /payments/ {
|
||||||
proxy_pass http://payments:8000/;
|
rewrite ^/payments/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://marriage_payments:8000;
|
||||||
proxy_http_version 1.1;
|
proxy_http_version 1.1;
|
||||||
proxy_set_header Host $host;
|
proxy_set_header Host $host;
|
||||||
proxy_set_header X-Real-IP $remote_addr;
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
|||||||
284
logs/api.log
284
logs/api.log
@@ -1,284 +0,0 @@
|
|||||||
2025-08-08 21:41:03 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 200 in 3 ms | body={"status":"ok","service":"auth"}
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | gateway/auth is healthy
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | Waiting profiles health: http://localhost:8080/profiles/health
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 200 in 1 ms | body={"status":"ok","service":"profiles"}
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | profiles is healthy
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | Waiting match health: http://localhost:8080/match/health
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP GET http://localhost:8080/match/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"match"}
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | match is healthy
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | Waiting chat health: http://localhost:8080/chat/health
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP GET http://localhost:8080/chat/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"chat"}
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | chat is healthy
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | Waiting payments health: http://localhost:8080/payments/health
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP GET http://localhost:8080/payments/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"payments"}
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | payments is healthy
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754656863.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 500 in 6 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:41:03 | ERROR | api_e2e | login unexpected status 500, expected [200]; body=Internal Server Error
|
|
||||||
2025-08-08 21:41:03 | INFO | api_e2e | Login failed for admin+1754656863.xaji0y@agency.dev: login unexpected status 500, expected [200]; body=Internal Server Error; will try register
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'admin+1754656863.xaji0y@agency.dev', 'password': '***hidden***', 'full_name': 'Corey Briggs', 'role': 'ADMIN'}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 500 in 7 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:41:03 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:41:03 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754656863.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:41:03 | DEBUG | api_e2e | ← 500 in 6 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:41:03 | ERROR | api_e2e | login unexpected status 500, expected [200]; body=Internal Server Error
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"auth"}
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | gateway/auth is healthy
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | Waiting profiles health: http://localhost:8080/profiles/health
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"profiles"}
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | profiles is healthy
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | Waiting match health: http://localhost:8080/match/health
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP GET http://localhost:8080/match/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"match"}
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | match is healthy
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | Waiting chat health: http://localhost:8080/chat/health
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP GET http://localhost:8080/chat/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"chat"}
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | chat is healthy
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | Waiting payments health: http://localhost:8080/payments/health
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP GET http://localhost:8080/payments/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"payments"}
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | payments is healthy
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754656992.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 500 in 6 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:43:12 | ERROR | api_e2e | login unexpected status 500, expected [200]; body=Internal Server Error
|
|
||||||
2025-08-08 21:43:12 | INFO | api_e2e | Login failed for admin+1754656992.xaji0y@agency.dev: login unexpected status 500, expected [200]; body=Internal Server Error; will try register
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'admin+1754656992.xaji0y@agency.dev', 'password': '***hidden***', 'full_name': 'Heather Franklin', 'role': 'ADMIN'}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 500 in 7 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:43:12 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:43:12 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754656992.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:43:12 | DEBUG | api_e2e | ← 500 in 9 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:43:12 | ERROR | api_e2e | login unexpected status 500, expected [200]; body=Internal Server Error
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 3 ms | body={"status":"ok","service":"auth"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | gateway/auth is healthy
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Waiting profiles health: http://localhost:8080/profiles/health
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 1 ms | body={"status":"ok","service":"profiles"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | profiles is healthy
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Waiting match health: http://localhost:8080/match/health
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP GET http://localhost:8080/match/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"match"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | match is healthy
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Waiting chat health: http://localhost:8080/chat/health
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP GET http://localhost:8080/chat/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"chat"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | chat is healthy
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Waiting payments health: http://localhost:8080/payments/health
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP GET http://localhost:8080/payments/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"payments"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | payments is healthy
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754657136.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 401 in 4 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:45:36 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Login failed for admin+1754657136.xaji0y@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'admin+1754657136.xaji0y@agency.dev', 'password': '***hidden***', 'full_name': 'Allison Sanders', 'role': 'ADMIN'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 500 in 227 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:45:36 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:45:36 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754657136.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 213 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MDEyZDZkNC01ZjgwLTQzMzktOTIxMC0wNzI3ZGU1OTAwOGMiLCJlbWFpbCI6ImFkbWluKzE3NTQ2NTcxMzYueGFqaTB5QGFnZW5jeS5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0NjU4MDM2fQ.GNe6OFWt4zPlFC-8eGjVEwV-b_mj5AO3HRu75C2oikU","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1MDEyZDZkNC01ZjgwLTQzMzktOTIxMC0wNzI3ZGU1OTAwOGMiLCJlbWFpbCI6ImFkbWluKzE3NTQ2NTcxMzYueGFqaTB5QGFnZW5jeS5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzI0OTEzNn0.OGorK2VQ9KmOTSpnSzN_jJfv5Tvu5QkYldiTlm-sP_Q","token_type":"bearer"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Registered+Login OK: admin+1754657136.xaji0y@agency.dev -> 5012d6d4-5f80-4339-9210-0727de59008c
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user1+1754657136.6dpbhs@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 401 in 3 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:45:36 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Login failed for user1+1754657136.6dpbhs@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'user1+1754657136.6dpbhs@agency.dev', 'password': '***hidden***', 'full_name': 'Joshua Harris', 'role': 'CLIENT'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 500 in 225 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:45:36 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:45:36 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user1+1754657136.6dpbhs@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMDhmODg4MS04ZThhLTRiZDQtODNmNy02NGFjN2MwYjQzODQiLCJlbWFpbCI6InVzZXIxKzE3NTQ2NTcxMzYuNmRwYmhzQGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDY1ODAzNn0.JYz3xrtGtQ6V0g14CWinTVj1P1cz8cWDQSNz_Z-e64k","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJhMDhmODg4MS04ZThhLTRiZDQtODNmNy02NGFjN2MwYjQzODQiLCJlbWFpbCI6InVzZXIxKzE3NTQ2NTcxMzYuNmRwYmhzQGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTcyNDkxMzZ9.IfmVSwhWJbQjFj1mSmhN9qV20CYvHwy4aUuaaictCEI","token_type":"bearer"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Registered+Login OK: user1+1754657136.6dpbhs@agency.dev -> a08f8881-8e8a-4bd4-83f7-64ac7c0b4384
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user2+1754657136.ahxthv@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | ← 401 in 4 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:45:36 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:45:36 | INFO | api_e2e | Login failed for user2+1754657136.ahxthv@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 21:45:36 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'user2+1754657136.ahxthv@agency.dev', 'password': '***hidden***', 'full_name': 'Adrian Taylor', 'role': 'CLIENT'}
|
|
||||||
2025-08-08 21:45:37 | DEBUG | api_e2e | ← 500 in 225 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:45:37 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:45:37 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:45:37 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user2+1754657136.ahxthv@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:45:37 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2MDkyMTc5Zi03MmUwLTQ0NGMtYmI1YS0yNDRjYzVjMjNiMGIiLCJlbWFpbCI6InVzZXIyKzE3NTQ2NTcxMzYuYWh4dGh2QGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDY1ODAzN30.zhLgSUtLDDuisejsK-vIsxsplwEfXmSqtDdLuuOG6xY","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI2MDkyMTc5Zi03MmUwLTQ0NGMtYmI1YS0yNDRjYzVjMjNiMGIiLCJlbWFpbCI6InVzZXIyKzE3NTQ2NTcxMzYuYWh4dGh2QGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTcyNDkxMzd9.FFHAxduf-mGYZwFeP-SkLlRYtBV3U5v31hvtrAHbo30","token_type":"bearer"}
|
|
||||||
2025-08-08 21:45:37 | INFO | api_e2e | Registered+Login OK: user2+1754657136.ahxthv@agency.dev -> 6092179f-72e0-444c-bb5a-244cc5c23b0b
|
|
||||||
2025-08-08 21:45:37 | INFO | api_e2e | [1/3] Ensure profile for admin+1754657136.xaji0y@agency.dev (role=ADMIN)
|
|
||||||
2025-08-08 21:45:37 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/v1/profiles/me | headers={Authorization: Bearer eyJhbGciOiJI...} | body={}
|
|
||||||
2025-08-08 21:45:37 | DEBUG | api_e2e | ← 403 in 1 ms | body={"detail":"Not authenticated"}
|
|
||||||
2025-08-08 21:45:37 | ERROR | api_e2e | profiles/me unexpected status 403, expected [200, 404]; body={"detail":"Not authenticated"}
|
|
||||||
2025-08-08 21:54:30 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:54:30 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:54:30 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:55:18 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:55:18 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:55:18 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:55:29 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:55:29 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:55:29 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | ← 200 in 14 ms | body={"status":"ok","service":"auth"}
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | gateway/auth is healthy
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | Waiting profiles health: http://localhost:8080/profiles/health
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | ← 200 in 5 ms | body={"status":"ok","service":"profiles"}
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | profiles is healthy
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | Waiting match health: http://localhost:8080/match/health
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP GET http://localhost:8080/match/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | ← 200 in 3 ms | body={"status":"ok","service":"match"}
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | match is healthy
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | Waiting chat health: http://localhost:8080/chat/health
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP GET http://localhost:8080/chat/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"chat"}
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | chat is healthy
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | Waiting payments health: http://localhost:8080/payments/health
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP GET http://localhost:8080/payments/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"payments"}
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | payments is healthy
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754657779.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | ← 401 in 10 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:56:19 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:56:19 | INFO | api_e2e | Login failed for admin+1754657779.xaji0y@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 21:56:19 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'admin+1754657779.xaji0y@agency.dev', 'password': '***hidden***', 'full_name': 'Eric Roberson', 'role': 'ADMIN'}
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | ← 500 in 230 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:56:20 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:56:20 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754657779.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | ← 200 in 213 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MDZhYmI3OS1kOWI2LTRjMzAtOGQ0ZC0xOTUwYWI2MTE5ZGUiLCJlbWFpbCI6ImFkbWluKzE3NTQ2NTc3NzkueGFqaTB5QGFnZW5jeS5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0NjU4NjgwfQ.fopkkb3_QSCoDCkyYDVeQRCJse2VFP2cHDYx8QkZ6eY","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MDZhYmI3OS1kOWI2LTRjMzAtOGQ0ZC0xOTUwYWI2MTE5ZGUiLCJlbWFpbCI6ImFkbWluKzE3NTQ2NTc3NzkueGFqaTB5QGFnZW5jeS5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzI0OTc4MH0.vegho7J95DF_XuJqKaoTa79RcQHEhM-O4Uo-iDF4s2M","token_type":"bearer"}
|
|
||||||
2025-08-08 21:56:20 | INFO | api_e2e | Registered+Login OK: admin+1754657779.xaji0y@agency.dev -> 406abb79-d9b6-4c30-8d4d-1950ab6119de
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user1+1754657780.6dpbhs@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | ← 401 in 3 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:56:20 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:56:20 | INFO | api_e2e | Login failed for user1+1754657780.6dpbhs@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'user1+1754657780.6dpbhs@agency.dev', 'password': '***hidden***', 'full_name': 'Stephen Garcia', 'role': 'CLIENT'}
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | ← 500 in 224 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:56:20 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:56:20 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user1+1754657780.6dpbhs@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMjM2YzNkZS01YzllLTRiNGMtODE5My1hZjIwODI0MDgxZGUiLCJlbWFpbCI6InVzZXIxKzE3NTQ2NTc3ODAuNmRwYmhzQGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDY1ODY4MH0.8lkRa-uwaI5MD5Bz-NQPYuxuFb84lAroVX9nqwIwSWU","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyMjM2YzNkZS01YzllLTRiNGMtODE5My1hZjIwODI0MDgxZGUiLCJlbWFpbCI6InVzZXIxKzE3NTQ2NTc3ODAuNmRwYmhzQGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTcyNDk3ODB9.bMMXN_To2KRtLME0NiY1BkHfpQXkkPm4WOv4KUD3PYs","token_type":"bearer"}
|
|
||||||
2025-08-08 21:56:20 | INFO | api_e2e | Registered+Login OK: user1+1754657780.6dpbhs@agency.dev -> 2236c3de-5c9e-4b4c-8193-af20824081de
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user2+1754657780.ahxthv@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | ← 401 in 4 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:56:20 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 21:56:20 | INFO | api_e2e | Login failed for user2+1754657780.ahxthv@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 21:56:20 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'user2+1754657780.ahxthv@agency.dev', 'password': '***hidden***', 'full_name': 'Colleen Morrow', 'role': 'CLIENT'}
|
|
||||||
2025-08-08 21:56:21 | DEBUG | api_e2e | ← 500 in 226 ms | body=Internal Server Error
|
|
||||||
2025-08-08 21:56:21 | ERROR | api_e2e | register unexpected status 500, expected [200, 201]; body=Internal Server Error
|
|
||||||
2025-08-08 21:56:21 | WARNING | api_e2e | register returned non-2xx: register unexpected status 500, expected [200, 201]; body=Internal Server Error — will try login anyway
|
|
||||||
2025-08-08 21:56:21 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user2+1754657780.ahxthv@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 21:56:21 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NzFlZTBjMi05ZTIyLTQ1OTYtYWZhOS03YWJiZmQxMzBlODYiLCJlbWFpbCI6InVzZXIyKzE3NTQ2NTc3ODAuYWh4dGh2QGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDY1ODY4MX0.Xx_ASHRjT8B_4EBpndcQoKcys8lJ_uJIN0log_f-2Ss","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI4NzFlZTBjMi05ZTIyLTQ1OTYtYWZhOS03YWJiZmQxMzBlODYiLCJlbWFpbCI6InVzZXIyKzE3NTQ2NTc3ODAuYWh4dGh2QGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTcyNDk3ODF9.hK54H_2APL3FgjfUNdmoHxJaC5BMfw3XokhticH5pKQ","token_type":"bearer"}
|
|
||||||
2025-08-08 21:56:21 | INFO | api_e2e | Registered+Login OK: user2+1754657780.ahxthv@agency.dev -> 871ee0c2-9e22-4596-afa9-7abbfd130e86
|
|
||||||
2025-08-08 21:56:21 | INFO | api_e2e | [1/3] Ensure profile for admin+1754657779.xaji0y@agency.dev (role=ADMIN)
|
|
||||||
2025-08-08 21:56:21 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/v1/profiles/me | headers={Authorization: Bearer eyJhbGciOiJI...} | body={}
|
|
||||||
2025-08-08 21:56:21 | DEBUG | api_e2e | ← 403 in 2 ms | body={"detail":"Not authenticated"}
|
|
||||||
2025-08-08 21:56:21 | ERROR | api_e2e | profiles/me unexpected status 403, expected [200, 404]; body={"detail":"Not authenticated"}
|
|
||||||
2025-08-08 22:21:56 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 22:21:56 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 22:21:56 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 22:21:56 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:21:59 | DEBUG | api_e2e | ← 502 in 3056 ms | body=<html>
|
|
||||||
<head><title>502 Bad Gateway</title></head>
|
|
||||||
<body>
|
|
||||||
<center><h1>502 Bad Gateway</h1></center>
|
|
||||||
<hr><center>nginx/1.29.0</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
2025-08-08 22:21:59 | ERROR | api_e2e | gateway/auth/health unexpected status 502, expected [200]; body=<html>
|
|
||||||
<head><title>502 Bad Gateway</title></head>
|
|
||||||
<body>
|
|
||||||
<center><h1>502 Bad Gateway</h1></center>
|
|
||||||
<hr><center>nginx/1.29.0</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
2025-08-08 22:22:00 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:22:04 | DEBUG | api_e2e | ← 502 in 3095 ms | body=<html>
|
|
||||||
<head><title>502 Bad Gateway</title></head>
|
|
||||||
<body>
|
|
||||||
<center><h1>502 Bad Gateway</h1></center>
|
|
||||||
<hr><center>nginx/1.29.0</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
2025-08-08 22:22:04 | ERROR | api_e2e | gateway/auth/health unexpected status 502, expected [200]; body=<html>
|
|
||||||
<head><title>502 Bad Gateway</title></head>
|
|
||||||
<body>
|
|
||||||
<center><h1>502 Bad Gateway</h1></center>
|
|
||||||
<hr><center>nginx/1.29.0</center>
|
|
||||||
</body>
|
|
||||||
</html>
|
|
||||||
|
|
||||||
2025-08-08 22:22:05 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | === API E2E START ===
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | BASE_URL=http://localhost:8080 clients=2 domain=agency.dev
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | Waiting gateway/auth health: http://localhost:8080/auth/health
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP GET http://localhost:8080/auth/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | ← 200 in 3 ms | body={"status":"ok","service":"auth"}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | gateway/auth is healthy
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | Waiting profiles health: http://localhost:8080/profiles/health
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | ← 200 in 3 ms | body={"status":"ok","service":"profiles"}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | profiles is healthy
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | Waiting match health: http://localhost:8080/match/health
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP GET http://localhost:8080/match/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"match"}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | match is healthy
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | Waiting chat health: http://localhost:8080/chat/health
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP GET http://localhost:8080/chat/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"chat"}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | chat is healthy
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | Waiting payments health: http://localhost:8080/payments/health
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP GET http://localhost:8080/payments/health | headers={Authorization: Bearer } | body={}
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | ← 200 in 2 ms | body={"status":"ok","service":"payments"}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | payments is healthy
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754659903.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | ← 401 in 4 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 22:31:43 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 22:31:43 | INFO | api_e2e | Login failed for admin+1754659903.xaji0y@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 22:31:43 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'admin+1754659903.xaji0y@agency.dev', 'password': '***hidden***', 'full_name': 'Kimberly Banks', 'role': 'ADMIN'}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | ← 201 in 227 ms | body={"id":"caf24a32-42bd-491e-9ff3-31e2eb0211ed","email":"admin+1754659903.xaji0y@agency.dev","full_name":"Kimberly Banks","role":"ADMIN","is_active":true}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'admin+1754659903.xaji0y@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjYWYyNGEzMi00MmJkLTQ5MWUtOWZmMy0zMWUyZWIwMjExZWQiLCJlbWFpbCI6ImFkbWluKzE3NTQ2NTk5MDMueGFqaTB5QGFnZW5jeS5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoiYWNjZXNzIiwiZXhwIjoxNzU0NjYwODA0fQ.TBg4_Xu2js-yD6aIjx-BAt3n8MJtM4K5Ck1Yc-ZG8sg","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJjYWYyNGEzMi00MmJkLTQ5MWUtOWZmMy0zMWUyZWIwMjExZWQiLCJlbWFpbCI6ImFkbWluKzE3NTQ2NTk5MDMueGFqaTB5QGFnZW5jeS5kZXYiLCJyb2xlIjoiQURNSU4iLCJ0eXBlIjoicmVmcmVzaCIsImV4cCI6MTc1NzI1MTkwNH0.W0AsJT8t9QQTZhKKTiKk0Q-pZtpnCxp_Kw75h1f7yJA","token_type":"bearer"}
|
|
||||||
2025-08-08 22:31:44 | INFO | api_e2e | Registered+Login OK: admin+1754659903.xaji0y@agency.dev -> caf24a32-42bd-491e-9ff3-31e2eb0211ed
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user1+1754659904.6dpbhs@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | ← 401 in 4 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 22:31:44 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 22:31:44 | INFO | api_e2e | Login failed for user1+1754659904.6dpbhs@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'user1+1754659904.6dpbhs@agency.dev', 'password': '***hidden***', 'full_name': 'Jose Meyer', 'role': 'CLIENT'}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | ← 201 in 217 ms | body={"id":"f7f05b43-00f6-436d-ab66-6e81c5ccbc33","email":"user1+1754659904.6dpbhs@agency.dev","full_name":"Jose Meyer","role":"CLIENT","is_active":true}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user1+1754659904.6dpbhs@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmN2YwNWI0My0wMGY2LTQzNmQtYWI2Ni02ZTgxYzVjY2JjMzMiLCJlbWFpbCI6InVzZXIxKzE3NTQ2NTk5MDQuNmRwYmhzQGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDY2MDgwNH0.CJA6gG8WJdEPMSQSKLAPBIRY3JMA34TGjveNICF_d-I","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJmN2YwNWI0My0wMGY2LTQzNmQtYWI2Ni02ZTgxYzVjY2JjMzMiLCJlbWFpbCI6InVzZXIxKzE3NTQ2NTk5MDQuNmRwYmhzQGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTcyNTE5MDR9.aYdrjuY5smBsgrF_6EtP83P2d_700yVIdlWDbPXM5DU","token_type":"bearer"}
|
|
||||||
2025-08-08 22:31:44 | INFO | api_e2e | Registered+Login OK: user1+1754659904.6dpbhs@agency.dev -> f7f05b43-00f6-436d-ab66-6e81c5ccbc33
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user2+1754659904.ahxthv@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | ← 401 in 3 ms | body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 22:31:44 | ERROR | api_e2e | login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}
|
|
||||||
2025-08-08 22:31:44 | INFO | api_e2e | Login failed for user2+1754659904.ahxthv@agency.dev: login unexpected status 401, expected [200]; body={"detail":"Invalid credentials"}; will try register
|
|
||||||
2025-08-08 22:31:44 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/register | headers={Authorization: Bearer } | body={'email': 'user2+1754659904.ahxthv@agency.dev', 'password': '***hidden***', 'full_name': 'Teresa Mckenzie', 'role': 'CLIENT'}
|
|
||||||
2025-08-08 22:31:45 | DEBUG | api_e2e | ← 201 in 225 ms | body={"id":"540546d6-563c-452c-b4bc-b0a0ce977b50","email":"user2+1754659904.ahxthv@agency.dev","full_name":"Teresa Mckenzie","role":"CLIENT","is_active":true}
|
|
||||||
2025-08-08 22:31:45 | DEBUG | api_e2e | HTTP POST http://localhost:8080/auth/v1/token | headers={Authorization: Bearer } | body={'email': 'user2+1754659904.ahxthv@agency.dev', 'password': '***hidden***'}
|
|
||||||
2025-08-08 22:31:45 | DEBUG | api_e2e | ← 200 in 212 ms | body={"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NDA1NDZkNi01NjNjLTQ1MmMtYjRiYy1iMGEwY2U5NzdiNTAiLCJlbWFpbCI6InVzZXIyKzE3NTQ2NTk5MDQuYWh4dGh2QGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6ImFjY2VzcyIsImV4cCI6MTc1NDY2MDgwNX0.wYH9Tf_Q1oz0u_hxzyLYrOmAur_RxJyySxkJgxXOIqY","refresh_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI1NDA1NDZkNi01NjNjLTQ1MmMtYjRiYy1iMGEwY2U5NzdiNTAiLCJlbWFpbCI6InVzZXIyKzE3NTQ2NTk5MDQuYWh4dGh2QGFnZW5jeS5kZXYiLCJyb2xlIjoiQ0xJRU5UIiwidHlwZSI6InJlZnJlc2giLCJleHAiOjE3NTcyNTE5MDV9.TsULoLRT4s40aOD0IUE6uamDyKAvSHiw9AW9EfacVeo","token_type":"bearer"}
|
|
||||||
2025-08-08 22:31:45 | INFO | api_e2e | Registered+Login OK: user2+1754659904.ahxthv@agency.dev -> 540546d6-563c-452c-b4bc-b0a0ce977b50
|
|
||||||
2025-08-08 22:31:45 | INFO | api_e2e | [1/3] Ensure profile for admin+1754659903.xaji0y@agency.dev (role=ADMIN)
|
|
||||||
2025-08-08 22:31:45 | DEBUG | api_e2e | HTTP GET http://localhost:8080/profiles/v1/profiles/me | headers={Authorization: Bearer eyJhbGciOiJI...} | body={}
|
|
||||||
2025-08-08 22:31:45 | DEBUG | api_e2e | ← 403 in 2 ms | body={"detail":"Not authenticated"}
|
|
||||||
2025-08-08 22:31:45 | ERROR | api_e2e | profiles/me unexpected status 403, expected [200, 404]; body={"detail":"Not authenticated"}
|
|
||||||
@@ -24,7 +24,12 @@ DEFAULT_BASE_URL = os.getenv("BASE_URL", "http://localhost:8080")
|
|||||||
DEFAULT_PASSWORD = os.getenv("PASS", "secret123")
|
DEFAULT_PASSWORD = os.getenv("PASS", "secret123")
|
||||||
DEFAULT_CLIENTS = int(os.getenv("CLIENTS", "2"))
|
DEFAULT_CLIENTS = int(os.getenv("CLIENTS", "2"))
|
||||||
DEFAULT_EMAIL_DOMAIN = os.getenv("EMAIL_DOMAIN", "agency.dev")
|
DEFAULT_EMAIL_DOMAIN = os.getenv("EMAIL_DOMAIN", "agency.dev")
|
||||||
DEFAULT_LOG_FILE = os.getenv("LOG_FILE", "logs/api.log")
|
DEFAULT_LOG_FILE = os.getenv("LOG_FILE", "logs/api.log")trevor@trevor-pc:/home/data/marriage/scripts$ ./e2e.sh
|
||||||
|
=== E2E smoke test start ===
|
||||||
|
BASE_URL: http://localhost:8080
|
||||||
|
|
||||||
|
[23:20:10] Waiting gateway at http://localhost:8080/auth/health (allowed: 200)
|
||||||
|
|
||||||
DEFAULT_TIMEOUT = float(os.getenv("HTTP_TIMEOUT", "10.0"))
|
DEFAULT_TIMEOUT = float(os.getenv("HTTP_TIMEOUT", "10.0"))
|
||||||
|
|
||||||
# -------------------------
|
# -------------------------
|
||||||
|
|||||||
218
scripts/patch.sh
218
scripts/patch.sh
@@ -1,120 +1,134 @@
|
|||||||
cat > scripts/fix_chat_uuid_schemas.sh <<'SH'
|
#!/usr/bin/env bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
svc_dir="services/chat/src/app/schemas"
|
CONF="infra/gateway/nginx.conf"
|
||||||
test -d "$svc_dir" || { echo "Not found: $svc_dir"; exit 1; }
|
[[ -f "$CONF" ]] || { echo "[ERR] $CONF not found"; exit 1; }
|
||||||
|
cp "$CONF" "$CONF.bak.$(date +%s)"
|
||||||
|
echo "[OK] backup saved"
|
||||||
|
|
||||||
python3 - <<'PY'
|
cat > "$CONF" <<'NGINX'
|
||||||
import re
|
server {
|
||||||
from pathlib import Path
|
listen 80;
|
||||||
|
server_name _;
|
||||||
|
|
||||||
root = Path("services/chat/src/app/schemas")
|
# Docker DNS
|
||||||
|
resolver 127.0.0.11 ipv6=off valid=10s;
|
||||||
|
|
||||||
def ensure_future_and_uuid(text: str) -> str:
|
# Health of gateway itself
|
||||||
lines = text.splitlines(keepends=True)
|
location = /health {
|
||||||
changed = False
|
default_type application/json;
|
||||||
|
return 200 '{"status":"ok","gateway":"nginx"}';
|
||||||
|
}
|
||||||
|
|
||||||
# Удалим дубли future-импорта
|
# ===== Unified API Docs (docs aggregator) =====
|
||||||
fut_pat = re.compile(r'^\s*from\s+__future__\s+import\s+annotations\s*(#.*)?$\n?', re.M)
|
location = /docs {
|
||||||
if fut_pat.search(text):
|
proxy_pass http://marriage_docs:8000/docs;
|
||||||
lines = [ln for ln in lines if not fut_pat.match(ln)]
|
proxy_http_version 1.1;
|
||||||
changed = True
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
# Вставим future после докстринга (если он есть)
|
location = /redoc {
|
||||||
i = 0
|
proxy_pass http://marriage_docs:8000/redoc;
|
||||||
while i < len(lines) and (lines[i].strip()=="" or lines[i].lstrip().startswith("#!") or re.match(r'^\s*#.*coding[:=]', lines[i])):
|
proxy_http_version 1.1;
|
||||||
i += 1
|
proxy_set_header Host $host;
|
||||||
insert_at = i
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
if i < len(lines) and re.match(r'^\s*[rubfRUBF]*[\'"]{3}', lines[i]): # докстринг
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
q = '"""' if '"""' in lines[i] else "'''"
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
j = i
|
}
|
||||||
while j < len(lines):
|
|
||||||
if q in lines[j] and j != i:
|
|
||||||
j += 1
|
|
||||||
break
|
|
||||||
j += 1
|
|
||||||
insert_at = min(j, len(lines))
|
|
||||||
|
|
||||||
if not any("from __future__ import annotations" in ln for ln in lines):
|
location = /openapi.json {
|
||||||
lines.insert(insert_at, "from __future__ import annotations\n")
|
proxy_pass http://marriage_docs:8000/openapi.json;
|
||||||
changed = True
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
txt = "".join(lines)
|
location = /refresh {
|
||||||
if "from uuid import UUID" not in txt:
|
proxy_pass http://marriage_docs:8000/refresh;
|
||||||
# вставим сразу после future
|
proxy_http_version 1.1;
|
||||||
# найдём позицию future
|
proxy_set_header Host $host;
|
||||||
pos = next((k for k,ln in enumerate(lines) if "from __future__ import annotations" in ln), 0)
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
lines.insert(pos+1, "from uuid import UUID\n")
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
changed = True
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
return "".join(lines), changed
|
location = /_health {
|
||||||
|
proxy_pass http://marriage_docs:8000/_health;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
def set_config(text: str) -> str:
|
# ===== Microservices (prefix strip) =====
|
||||||
# Pydantic v2: добавим ConfigDict и model_config, если их нет
|
location /auth/ {
|
||||||
if "ConfigDict" not in text:
|
rewrite ^/auth/(.*)$ /$1 break;
|
||||||
text = re.sub(r'from pydantic import ([^\n]+)',
|
proxy_pass http://marriage_auth:8000;
|
||||||
lambda m: m.group(0).replace(m.group(1), (m.group(1) + ", ConfigDict").replace(", ConfigDict,"," , ConfigDict,")),
|
proxy_http_version 1.1;
|
||||||
text, count=1) if "from pydantic import" in text else ("from pydantic import BaseModel, ConfigDict\n" + text)
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
proxy_set_header Authorization $http_authorization;
|
||||||
|
}
|
||||||
|
|
||||||
# В каждую модель, наследующую BaseModel, которая заканчивается на Read/Out, добавим from_attributes
|
location /profiles/ {
|
||||||
def inject_cfg(block: str) -> str:
|
rewrite ^/profiles/(.*)$ /$1 break;
|
||||||
# если уже есть model_config/orm_mode — не трогаем
|
proxy_pass http://marriage_profiles:8000;
|
||||||
if "model_config" in block or "class Config" in block:
|
proxy_http_version 1.1;
|
||||||
return block
|
proxy_set_header Host $host;
|
||||||
# аккуратно добавим model_config сверху класса
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
header, body = block.split(":", 1)
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
return header + ":\n model_config = ConfigDict(from_attributes=True)\n" + body
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
|
||||||
# Разобьём по классам
|
location /match/ {
|
||||||
parts = re.split(r'(\nclass\s+[A-Za-z0-9_]+\s*\([^\)]*BaseModel[^\)]*\)\s*:)', text)
|
rewrite ^/match/(.*)$ /$1 break;
|
||||||
if len(parts) > 1:
|
proxy_pass http://marriage_match:8000;
|
||||||
out = [parts[0]]
|
proxy_http_version 1.1;
|
||||||
for i in range(1, len(parts), 2):
|
proxy_set_header Host $host;
|
||||||
head = parts[i]
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
body = parts[i+1]
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
m = re.search(r'class\s+([A-Za-z0-9_]+)\s*\(', head)
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
name = m.group(1) if m else ""
|
}
|
||||||
if name.endswith(("Read","Out")):
|
|
||||||
out.append(inject_cfg(head + body))
|
|
||||||
else:
|
|
||||||
out.append(head + body)
|
|
||||||
text = "".join(out)
|
|
||||||
return text
|
|
||||||
|
|
||||||
def fix_uuid_fields(text: str) -> str:
|
location /chat/ {
|
||||||
# Преобразуем только в классах Read/Out
|
rewrite ^/chat/(.*)$ /$1 break;
|
||||||
def repl_in_class(cls_text: str) -> str:
|
proxy_pass http://marriage_chat:8000;
|
||||||
# заменяем аннотации полей
|
proxy_http_version 1.1;
|
||||||
repl = {
|
proxy_set_header Host $host;
|
||||||
r'(^\s*)(id)\s*:\s*str(\b)': r'\1\2: UUID\3',
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
r'(^\s*)(pair_id)\s*:\s*str(\b)': r'\1\2: UUID\3',
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
r'(^\s*)(room_id)\s*:\s*str(\b)': r'\1\2: UUID\3',
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
r'(^\s*)(sender_id)\s*:\s*str(\b)': r'\1\2: UUID\3',
|
}
|
||||||
r'(^\s*)(user_id)\s*:\s*str(\b)': r'\1\2: UUID\3',
|
|
||||||
}
|
|
||||||
for pat, sub in repl.items():
|
|
||||||
cls_text = re.sub(pat, sub, cls_text, flags=re.M)
|
|
||||||
return cls_text
|
|
||||||
|
|
||||||
patt = re.compile(r'(\nclass\s+[A-Za-z0-9_]+(Read|Out)\s*\([^\)]*BaseModel[^\)]*\)\s*:\n(?:\s+.*\n)+)', re.M)
|
location /payments/ {
|
||||||
return patt.sub(lambda m: repl_in_class(m.group(1)), text)
|
rewrite ^/payments/(.*)$ /$1 break;
|
||||||
|
proxy_pass http://marriage_payments:8000;
|
||||||
|
proxy_http_version 1.1;
|
||||||
|
proxy_set_header Host $host;
|
||||||
|
proxy_set_header X-Real-IP $remote_addr;
|
||||||
|
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||||
|
proxy_set_header X-Forwarded-Proto $scheme;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
NGINX
|
||||||
|
|
||||||
changed_any = False
|
echo "[OK] nginx.conf updated"
|
||||||
for p in root.glob("**/*.py"):
|
|
||||||
txt = p.read_text()
|
|
||||||
new, c1 = ensure_future_and_uuid(txt)
|
|
||||||
new = set_config(new)
|
|
||||||
new2 = fix_uuid_fields(new)
|
|
||||||
if new2 != txt:
|
|
||||||
p.write_text(new2)
|
|
||||||
print("fixed:", p)
|
|
||||||
changed_any = True
|
|
||||||
|
|
||||||
print("DONE" if changed_any else "NO-CHANGES")
|
|
||||||
PY
|
|
||||||
|
|
||||||
echo "[docker] rebuild & restart chat…"
|
|
||||||
docker compose build --no-cache chat >/dev/null
|
|
||||||
docker compose restart chat
|
|
||||||
|
|
||||||
|
echo "[INFO] restarting gateway..."
|
||||||
|
docker compose restart gateway >/dev/null
|
||||||
|
|
||||||
|
echo "[INFO] quick checks:"
|
||||||
|
echo -n "gateway: "; curl -s http://localhost:8080/health; echo
|
||||||
|
echo -n "docs/_health:"; curl -s http://localhost:8080/_health; echo
|
||||||
|
for svc in auth profiles match chat payments; do
|
||||||
|
code=$(curl -s -o /dev/null -w "%{http_code}" "http://localhost:8080/$svc/health")
|
||||||
|
echo "$svc/health: $code"
|
||||||
|
done
|
||||||
|
|||||||
8
services/docs/Dockerfile
Normal file
8
services/docs/Dockerfile
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
FROM python:3.12-slim
|
||||||
|
|
||||||
|
WORKDIR /app
|
||||||
|
COPY requirements.txt .
|
||||||
|
RUN pip install --no-cache-dir -r requirements.txt
|
||||||
|
COPY main.py .
|
||||||
|
|
||||||
|
CMD ["uvicorn", "main:app", "--host", "0.0.0.0", "--port", "8000"]
|
||||||
127
services/docs/main.py
Normal file
127
services/docs/main.py
Normal file
@@ -0,0 +1,127 @@
|
|||||||
|
import asyncio
|
||||||
|
import logging
|
||||||
|
from typing import Dict, Optional
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from fastapi import FastAPI
|
||||||
|
from fastapi.openapi.utils import get_openapi
|
||||||
|
from fastapi.responses import JSONResponse, RedirectResponse
|
||||||
|
|
||||||
|
logging.basicConfig(level=logging.INFO, format="%(asctime)s [%(levelname)s] %(message)s")
|
||||||
|
log = logging.getLogger("docs")
|
||||||
|
|
||||||
|
app = FastAPI(title="Unified Marriage API Docs", version="1.0.0")
|
||||||
|
|
||||||
|
# Переопределяем OpenAPI, чтобы /docs использовал объединённую схему
|
||||||
|
def custom_openapi():
|
||||||
|
global _cached_openapi
|
||||||
|
import asyncio
|
||||||
|
if _cached_openapi is None or len((_cached_openapi.get("paths") or {})) == 0:
|
||||||
|
_cached_openapi = asyncio.run(build_cache())
|
||||||
|
return _cached_openapi
|
||||||
|
|
||||||
|
app.openapi = custom_openapi
|
||||||
|
|
||||||
|
# Используем ИМЕНА КОНТЕЙНЕРОВ и внутренний порт 8000
|
||||||
|
MICROSERVICES: Dict[str, str] = {
|
||||||
|
"auth": "http://marriage_auth:8000/openapi.json",
|
||||||
|
"profiles": "http://marriage_profiles:8000/openapi.json",
|
||||||
|
"match": "http://marriage_match:8000/openapi.json",
|
||||||
|
"chat": "http://marriage_chat:8000/openapi.json",
|
||||||
|
"payments": "http://marriage_payments:8000/openapi.json",
|
||||||
|
}
|
||||||
|
|
||||||
|
_cached_openapi: Optional[dict] = None
|
||||||
|
|
||||||
|
async def fetch_spec(client: httpx.AsyncClient, name: str, url: str, retries: int = 6, delay: float = 2.0) -> Optional[dict]:
|
||||||
|
for attempt in range(1, retries + 1):
|
||||||
|
try:
|
||||||
|
r = await client.get(url)
|
||||||
|
r.raise_for_status()
|
||||||
|
spec = r.json()
|
||||||
|
log.info("Loaded OpenAPI from %s: %s paths", name, len((spec or {}).get("paths", {}) or {}))
|
||||||
|
return spec
|
||||||
|
except Exception as e:
|
||||||
|
log.warning("Attempt %s/%s failed for %s: %s", attempt, retries, name, e)
|
||||||
|
await asyncio.sleep(delay)
|
||||||
|
log.error("Could not load OpenAPI from %s after %s attempts", name, retries)
|
||||||
|
return None
|
||||||
|
|
||||||
|
def merge_specs(specs_by_name: Dict[str, Optional[dict]]) -> dict:
|
||||||
|
merged_paths: Dict[str, dict] = {}
|
||||||
|
merged_components = {"schemas": {}}
|
||||||
|
|
||||||
|
for name, spec in specs_by_name.items():
|
||||||
|
if not spec:
|
||||||
|
continue
|
||||||
|
for path, methods in (spec.get("paths") or {}).items():
|
||||||
|
merged_paths[f"/{name}{path}"] = methods
|
||||||
|
merged_components["schemas"].update((spec.get("components") or {}).get("schemas", {}) or {})
|
||||||
|
|
||||||
|
openapi_schema = get_openapi(
|
||||||
|
title="Unified Marriage API",
|
||||||
|
version="1.0.0",
|
||||||
|
description="Combined OpenAPI schema for all microservices",
|
||||||
|
routes=[],
|
||||||
|
)
|
||||||
|
openapi_schema["servers"] = [{"url": "http://localhost:8080"}]
|
||||||
|
openapi_schema["paths"] = merged_paths
|
||||||
|
openapi_schema["components"] = merged_components
|
||||||
|
return openapi_schema
|
||||||
|
|
||||||
|
async def build_cache() -> dict:
|
||||||
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||||
|
tasks = [fetch_spec(client, name, url) for name, url in MICROSERVICES.items()]
|
||||||
|
specs = await asyncio.gather(*tasks)
|
||||||
|
|
||||||
|
specs_by_name = dict(zip(MICROSERVICES.keys(), specs))
|
||||||
|
merged = merge_specs(specs_by_name)
|
||||||
|
log.info("Unified OpenAPI built: %s paths", len(merged.get("paths", {})))
|
||||||
|
return merged
|
||||||
|
|
||||||
|
async def ensure_cache() -> dict:
|
||||||
|
"""Гарантируем, что кэш непустой: если paths == 0 — пересобираем."""
|
||||||
|
global _cached_openapi
|
||||||
|
if _cached_openapi is None or len((_cached_openapi.get("paths") or {})) == 0:
|
||||||
|
_cached_openapi = await build_cache()
|
||||||
|
return _cached_openapi
|
||||||
|
|
||||||
|
@app.on_event("startup")
|
||||||
|
async def _warmup_cache():
|
||||||
|
"""Always rebuild cache on startup so Swagger has full data."""
|
||||||
|
global _cached_openapi
|
||||||
|
try:
|
||||||
|
_cached_openapi = await build_cache()
|
||||||
|
log.info("[startup] Cache built: %s paths", len((_cached_openapi.get("paths") or {})))
|
||||||
|
except Exception as e:
|
||||||
|
log.error("[startup] Failed to build cache: %s", e)
|
||||||
|
@app.get("/", include_in_schema=False)
|
||||||
|
def root():
|
||||||
|
return RedirectResponse(url="/docs")
|
||||||
|
|
||||||
|
@app.get("/_health", include_in_schema=False)
|
||||||
|
def health():
|
||||||
|
return {"status": "ok", "cached_paths": len((_cached_openapi or {}).get("paths", {}))}
|
||||||
|
|
||||||
|
@app.get("/debug", include_in_schema=False)
|
||||||
|
async def debug():
|
||||||
|
out = {}
|
||||||
|
async with httpx.AsyncClient(timeout=5.0) as client:
|
||||||
|
for name, url in MICROSERVICES.items():
|
||||||
|
try:
|
||||||
|
r = await client.get(url)
|
||||||
|
out[name] = {"status": r.status_code, "len": len(r.text), "starts_with": r.text[:80]}
|
||||||
|
except Exception as e:
|
||||||
|
out[name] = {"error": str(e)}
|
||||||
|
return out
|
||||||
|
|
||||||
|
@app.get("/refresh", include_in_schema=False)
|
||||||
|
async def refresh():
|
||||||
|
global _cached_openapi
|
||||||
|
_cached_openapi = await build_cache()
|
||||||
|
return {"refreshed": True, "paths": len(_cached_openapi.get("paths", {}))}
|
||||||
|
|
||||||
|
@app.get("/openapi.json", include_in_schema=False)
|
||||||
|
async def unified_openapi():
|
||||||
|
data = await ensure_cache()
|
||||||
|
return JSONResponse(data)
|
||||||
3
services/docs/requirements.txt
Normal file
3
services/docs/requirements.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
fastapi
|
||||||
|
uvicorn
|
||||||
|
httpx
|
||||||
@@ -4,15 +4,23 @@ from sqlalchemy.orm import Session
|
|||||||
|
|
||||||
from app.db.session import get_db
|
from app.db.session import get_db
|
||||||
from app.core.security import get_current_user, require_roles, UserClaims
|
from app.core.security import get_current_user, require_roles, UserClaims
|
||||||
from app.schemas.payment import InvoiceCreate, InvoiceUpdate, InvoiceRead
|
from app.models import Invoice
|
||||||
|
from app.schemas.payment import InvoiceCreate, InvoiceUpdate, InvoiceRead, InvoiceOut
|
||||||
from app.services.payment_service import PaymentService
|
from app.services.payment_service import PaymentService
|
||||||
|
|
||||||
router = APIRouter(prefix="/v1/invoices", tags=["payments"])
|
router = APIRouter(prefix="/v1/invoices", tags=["payments"])
|
||||||
|
|
||||||
@router.post("", response_model=InvoiceRead, status_code=201)
|
@router.post("", response_model=InvoiceOut, status_code=201)
|
||||||
def create_invoice(payload: InvoiceCreate, db: Session = Depends(get_db),
|
def create_invoice(payload: InvoiceCreate, db: Session = Depends(get_db)):
|
||||||
_: UserClaims = Depends(require_roles("ADMIN","MATCHMAKER"))):
|
inv = Invoice(
|
||||||
return PaymentService(db).create_invoice(**payload.model_dump(exclude_none=True))
|
client_id=payload.client_id, # UUID
|
||||||
|
amount=payload.amount,
|
||||||
|
currency=payload.currency,
|
||||||
|
status="new",
|
||||||
|
)
|
||||||
|
db.add(inv)
|
||||||
|
db.commit()
|
||||||
|
db.refresh(inv)
|
||||||
|
return inv
|
||||||
|
|
||||||
@router.get("", response_model=list[InvoiceRead])
|
@router.get("", response_model=list[InvoiceRead])
|
||||||
def list_invoices(client_id: str | None = None, status: str | None = None,
|
def list_invoices(client_id: str | None = None, status: str | None = None,
|
||||||
|
|||||||
@@ -2,16 +2,17 @@ from __future__ import annotations
|
|||||||
import uuid
|
import uuid
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
from sqlalchemy import String, DateTime, Numeric
|
from sqlalchemy import String, DateTime, Numeric
|
||||||
from sqlalchemy.dialects.postgresql import UUID
|
|
||||||
from sqlalchemy.orm import Mapped, mapped_column
|
from sqlalchemy.orm import Mapped, mapped_column
|
||||||
from sqlalchemy.sql import func
|
from sqlalchemy.sql import func
|
||||||
|
from sqlalchemy.dialects.postgresql import UUID as PG_UUID
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
from app.db.session import Base
|
from app.db.session import Base
|
||||||
|
|
||||||
class Invoice(Base):
|
class Invoice(Base):
|
||||||
__tablename__ = "invoices"
|
__tablename__ = "invoices"
|
||||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
id = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid4)
|
||||||
client_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), index=True, nullable=False)
|
client_id = mapped_column(PG_UUID(as_uuid=True), nullable=False)
|
||||||
amount: Mapped[float] = mapped_column(Numeric(12,2), nullable=False)
|
amount: Mapped[float] = mapped_column(Numeric(12,2), nullable=False)
|
||||||
currency: Mapped[str] = mapped_column(String(3), default="USD")
|
currency: Mapped[str] = mapped_column(String(3), default="USD")
|
||||||
status: Mapped[str] = mapped_column(String(16), default="pending") # pending/paid/canceled
|
status: Mapped[str] = mapped_column(String(16), default="pending") # pending/paid/canceled
|
||||||
|
|||||||
@@ -1,11 +1,14 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
from datetime import datetime
|
||||||
from typing import Optional
|
from typing import Optional
|
||||||
from pydantic import BaseModel, ConfigDict
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
from uuid import UUID
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
class InvoiceCreate(BaseModel):
|
class InvoiceCreate(BaseModel):
|
||||||
client_id: str
|
client_id: UUID
|
||||||
amount: float
|
amount: int = Field(ge=0)
|
||||||
currency: str = "USD"
|
currency: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
|
|
||||||
class InvoiceUpdate(BaseModel):
|
class InvoiceUpdate(BaseModel):
|
||||||
@@ -15,10 +18,23 @@ class InvoiceUpdate(BaseModel):
|
|||||||
status: Optional[str] = None
|
status: Optional[str] = None
|
||||||
|
|
||||||
class InvoiceRead(BaseModel):
|
class InvoiceRead(BaseModel):
|
||||||
id: str
|
id: UUID
|
||||||
client_id: str
|
client_id: UUID
|
||||||
amount: float
|
amount: float
|
||||||
currency: str
|
currency: str
|
||||||
status: str
|
status: str
|
||||||
description: Optional[str] = None
|
description: Optional[str] = None
|
||||||
model_config = ConfigDict(from_attributes=True)
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceOut(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
client_id: UUID
|
||||||
|
amount: int
|
||||||
|
currency: str
|
||||||
|
status: str
|
||||||
|
created_at: datetime | None = None
|
||||||
|
updated_at: datetime | None = None
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
40
services/payments/src/app/schemas/payment.py.bak.1754729104
Normal file
40
services/payments/src/app/schemas/payment.py.bak.1754729104
Normal file
@@ -0,0 +1,40 @@
|
|||||||
|
from __future__ import annotations
|
||||||
|
from typing import Optional
|
||||||
|
from pydantic import BaseModel, ConfigDict
|
||||||
|
from uuid import UUID
|
||||||
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
|
class InvoiceCreate(BaseModel):
|
||||||
|
client_id: UUID
|
||||||
|
amount: int = Field(ge=0)
|
||||||
|
currency: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
|
||||||
|
class InvoiceUpdate(BaseModel):
|
||||||
|
amount: Optional[float] = None
|
||||||
|
currency: Optional[str] = None
|
||||||
|
description: Optional[str] = None
|
||||||
|
status: Optional[str] = None
|
||||||
|
|
||||||
|
class InvoiceRead(BaseModel):
|
||||||
|
id: str
|
||||||
|
client_id: str
|
||||||
|
amount: float
|
||||||
|
currency: str
|
||||||
|
status: str
|
||||||
|
description: Optional[str] = None
|
||||||
|
model_config = ConfigDict(from_attributes=True)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
class InvoiceOut(BaseModel):
|
||||||
|
id: UUID
|
||||||
|
client_id: UUID
|
||||||
|
amount: int
|
||||||
|
currency: str
|
||||||
|
status: str
|
||||||
|
created_at: str | None = None
|
||||||
|
# если есть другие UUID-поля — тоже как UUID
|
||||||
|
|
||||||
|
class Config:
|
||||||
|
from_attributes = True
|
||||||
Reference in New Issue
Block a user