From 7ecc556c7742535a2ba1daa34737433a587c4a50 Mon Sep 17 00:00:00 2001 From: "Andrey K. Choi" Date: Sat, 9 Aug 2025 23:36:52 +0900 Subject: [PATCH] API fully functional Api docs (SWAGGER, REDOC) available --- .gitignore | 5 +- docker-compose.yml | 14 +- infra/gateway/nginx.conf | 75 ++++- logs/api.log | 284 ------------------ scripts/api_e2e.py | 7 +- scripts/patch.sh | 218 +++++++------- services/docs/Dockerfile | 8 + services/docs/main.py | 127 ++++++++ services/docs/requirements.txt | 3 + .../payments/src/app/api/routes/payments.py | 20 +- services/payments/src/app/models/payment.py | 7 +- services/payments/src/app/schemas/payment.py | 26 +- .../src/app/schemas/payment.py.bak.1754729104 | 40 +++ 13 files changed, 422 insertions(+), 412 deletions(-) delete mode 100644 logs/api.log create mode 100644 services/docs/Dockerfile create mode 100644 services/docs/main.py create mode 100644 services/docs/requirements.txt create mode 100644 services/payments/src/app/schemas/payment.py.bak.1754729104 diff --git a/.gitignore b/.gitignore index 12e5f6d..d1d5c6c 100644 --- a/.gitignore +++ b/.gitignore @@ -24,4 +24,7 @@ build/ **/.python_packages/ **/.pytest_cache/ **/.ruff_cache/ -.history/ \ No newline at end of file +.history/ + +#scripts +scripts/ \ No newline at end of file diff --git a/docker-compose.yml b/docker-compose.yml index eb86f45..36656a4 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -100,6 +100,18 @@ services: ports: - "${PAYMENTS_PORT:-8005}:8000" 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: pgdata: diff --git a/infra/gateway/nginx.conf b/infra/gateway/nginx.conf index bcac648..d3c6583 100644 --- a/infra/gateway/nginx.conf +++ b/infra/gateway/nginx.conf @@ -2,49 +2,106 @@ server { listen 80; server_name _; + # Docker DNS + resolver 127.0.0.11 ipv6=off valid=10s; + # Health of gateway itself location = /health { default_type application/json; 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/ { - proxy_pass http://auth:8000/; - proxy_set_header Authorization $http_authorization; - proxy_set_header X-Forwarded-Proto $scheme; - proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_set_header Host $host; + rewrite ^/auth/(.*)$ /$1 break; + proxy_pass http://marriage_auth: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; + proxy_set_header Authorization $http_authorization; } + location /profiles/ { - proxy_pass http://profiles:8000/; + rewrite ^/profiles/(.*)$ /$1 break; + proxy_pass http://marriage_profiles: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; } + location /match/ { - proxy_pass http://match:8000/; + rewrite ^/match/(.*)$ /$1 break; + proxy_pass http://marriage_match: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; } + location /chat/ { - proxy_pass http://chat:8000/; + rewrite ^/chat/(.*)$ /$1 break; + proxy_pass http://marriage_chat: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; } + location /payments/ { - proxy_pass http://payments:8000/; + 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; diff --git a/logs/api.log b/logs/api.log deleted file mode 100644 index 9ef43d2..0000000 --- a/logs/api.log +++ /dev/null @@ -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= -502 Bad Gateway - -

502 Bad Gateway

-
nginx/1.29.0
- - - -2025-08-08 22:21:59 | ERROR | api_e2e | gateway/auth/health unexpected status 502, expected [200]; body= -502 Bad Gateway - -

502 Bad Gateway

-
nginx/1.29.0
- - - -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= -502 Bad Gateway - -

502 Bad Gateway

-
nginx/1.29.0
- - - -2025-08-08 22:22:04 | ERROR | api_e2e | gateway/auth/health unexpected status 502, expected [200]; body= -502 Bad Gateway - -

502 Bad Gateway

-
nginx/1.29.0
- - - -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"} diff --git a/scripts/api_e2e.py b/scripts/api_e2e.py index 7e9f8e5..19dc66b 100644 --- a/scripts/api_e2e.py +++ b/scripts/api_e2e.py @@ -24,7 +24,12 @@ DEFAULT_BASE_URL = os.getenv("BASE_URL", "http://localhost:8080") DEFAULT_PASSWORD = os.getenv("PASS", "secret123") DEFAULT_CLIENTS = int(os.getenv("CLIENTS", "2")) 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")) # ------------------------- diff --git a/scripts/patch.sh b/scripts/patch.sh index 31f69ce..37c0871 100755 --- a/scripts/patch.sh +++ b/scripts/patch.sh @@ -1,120 +1,134 @@ -cat > scripts/fix_chat_uuid_schemas.sh <<'SH' +#!/usr/bin/env bash set -euo pipefail -svc_dir="services/chat/src/app/schemas" -test -d "$svc_dir" || { echo "Not found: $svc_dir"; exit 1; } +CONF="infra/gateway/nginx.conf" +[[ -f "$CONF" ]] || { echo "[ERR] $CONF not found"; exit 1; } +cp "$CONF" "$CONF.bak.$(date +%s)" +echo "[OK] backup saved" -python3 - <<'PY' -import re -from pathlib import Path +cat > "$CONF" <<'NGINX' +server { + 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: - lines = text.splitlines(keepends=True) - changed = False + # Health of gateway itself + location = /health { + default_type application/json; + return 200 '{"status":"ok","gateway":"nginx"}'; + } - # Удалим дубли future-импорта - fut_pat = re.compile(r'^\s*from\s+__future__\s+import\s+annotations\s*(#.*)?$\n?', re.M) - if fut_pat.search(text): - lines = [ln for ln in lines if not fut_pat.match(ln)] - changed = True + # ===== 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; + } - # Вставим future после докстринга (если он есть) - i = 0 - while i < len(lines) and (lines[i].strip()=="" or lines[i].lstrip().startswith("#!") or re.match(r'^\s*#.*coding[:=]', lines[i])): - i += 1 - insert_at = i - if i < len(lines) and re.match(r'^\s*[rubfRUBF]*[\'"]{3}', lines[i]): # докстринг - q = '"""' if '"""' in lines[i] else "'''" - 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)) + 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; + } - if not any("from __future__ import annotations" in ln for ln in lines): - lines.insert(insert_at, "from __future__ import annotations\n") - changed = True + 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; + } - txt = "".join(lines) - if "from uuid import UUID" not in txt: - # вставим сразу после future - # найдём позицию future - pos = next((k for k,ln in enumerate(lines) if "from __future__ import annotations" in ln), 0) - lines.insert(pos+1, "from uuid import UUID\n") - changed = True + 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; + } - 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: - # Pydantic v2: добавим ConfigDict и model_config, если их нет - if "ConfigDict" not in text: - text = re.sub(r'from pydantic import ([^\n]+)', - lambda m: m.group(0).replace(m.group(1), (m.group(1) + ", ConfigDict").replace(", ConfigDict,"," , ConfigDict,")), - text, count=1) if "from pydantic import" in text else ("from pydantic import BaseModel, ConfigDict\n" + text) + # ===== Microservices (prefix strip) ===== + location /auth/ { + rewrite ^/auth/(.*)$ /$1 break; + proxy_pass http://marriage_auth: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; + proxy_set_header Authorization $http_authorization; + } - # В каждую модель, наследующую BaseModel, которая заканчивается на Read/Out, добавим from_attributes - def inject_cfg(block: str) -> str: - # если уже есть model_config/orm_mode — не трогаем - if "model_config" in block or "class Config" in block: - return block - # аккуратно добавим model_config сверху класса - header, body = block.split(":", 1) - return header + ":\n model_config = ConfigDict(from_attributes=True)\n" + body + location /profiles/ { + rewrite ^/profiles/(.*)$ /$1 break; + proxy_pass http://marriage_profiles: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; + } - # Разобьём по классам - parts = re.split(r'(\nclass\s+[A-Za-z0-9_]+\s*\([^\)]*BaseModel[^\)]*\)\s*:)', text) - if len(parts) > 1: - out = [parts[0]] - for i in range(1, len(parts), 2): - head = parts[i] - body = parts[i+1] - m = re.search(r'class\s+([A-Za-z0-9_]+)\s*\(', head) - 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 + location /match/ { + rewrite ^/match/(.*)$ /$1 break; + proxy_pass http://marriage_match: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; + } -def fix_uuid_fields(text: str) -> str: - # Преобразуем только в классах Read/Out - def repl_in_class(cls_text: str) -> str: - # заменяем аннотации полей - repl = { - r'(^\s*)(id)\s*:\s*str(\b)': r'\1\2: UUID\3', - r'(^\s*)(pair_id)\s*:\s*str(\b)': r'\1\2: UUID\3', - r'(^\s*)(room_id)\s*:\s*str(\b)': r'\1\2: UUID\3', - 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 + location /chat/ { + rewrite ^/chat/(.*)$ /$1 break; + proxy_pass http://marriage_chat: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; + } - patt = re.compile(r'(\nclass\s+[A-Za-z0-9_]+(Read|Out)\s*\([^\)]*BaseModel[^\)]*\)\s*:\n(?:\s+.*\n)+)', re.M) - return patt.sub(lambda m: repl_in_class(m.group(1)), text) + location /payments/ { + 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 -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 "[OK] nginx.conf updated" +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 diff --git a/services/docs/Dockerfile b/services/docs/Dockerfile new file mode 100644 index 0000000..f676143 --- /dev/null +++ b/services/docs/Dockerfile @@ -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"] \ No newline at end of file diff --git a/services/docs/main.py b/services/docs/main.py new file mode 100644 index 0000000..66c91de --- /dev/null +++ b/services/docs/main.py @@ -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) diff --git a/services/docs/requirements.txt b/services/docs/requirements.txt new file mode 100644 index 0000000..070f1ed --- /dev/null +++ b/services/docs/requirements.txt @@ -0,0 +1,3 @@ +fastapi +uvicorn +httpx \ No newline at end of file diff --git a/services/payments/src/app/api/routes/payments.py b/services/payments/src/app/api/routes/payments.py index 89b09ab..2f2413b 100644 --- a/services/payments/src/app/api/routes/payments.py +++ b/services/payments/src/app/api/routes/payments.py @@ -4,15 +4,23 @@ from sqlalchemy.orm import Session from app.db.session import get_db 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 - router = APIRouter(prefix="/v1/invoices", tags=["payments"]) -@router.post("", response_model=InvoiceRead, status_code=201) -def create_invoice(payload: InvoiceCreate, db: Session = Depends(get_db), - _: UserClaims = Depends(require_roles("ADMIN","MATCHMAKER"))): - return PaymentService(db).create_invoice(**payload.model_dump(exclude_none=True)) +@router.post("", response_model=InvoiceOut, status_code=201) +def create_invoice(payload: InvoiceCreate, db: Session = Depends(get_db)): + inv = Invoice( + 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]) def list_invoices(client_id: str | None = None, status: str | None = None, diff --git a/services/payments/src/app/models/payment.py b/services/payments/src/app/models/payment.py index 5ad8684..8df14d7 100644 --- a/services/payments/src/app/models/payment.py +++ b/services/payments/src/app/models/payment.py @@ -2,16 +2,17 @@ from __future__ import annotations import uuid from datetime import datetime from sqlalchemy import String, DateTime, Numeric -from sqlalchemy.dialects.postgresql import UUID from sqlalchemy.orm import Mapped, mapped_column from sqlalchemy.sql import func +from sqlalchemy.dialects.postgresql import UUID as PG_UUID +from uuid import uuid4 from app.db.session import Base class Invoice(Base): __tablename__ = "invoices" - id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4) - client_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), index=True, nullable=False) + id = mapped_column(PG_UUID(as_uuid=True), primary_key=True, default=uuid4) + client_id = mapped_column(PG_UUID(as_uuid=True), nullable=False) amount: Mapped[float] = mapped_column(Numeric(12,2), nullable=False) currency: Mapped[str] = mapped_column(String(3), default="USD") status: Mapped[str] = mapped_column(String(16), default="pending") # pending/paid/canceled diff --git a/services/payments/src/app/schemas/payment.py b/services/payments/src/app/schemas/payment.py index 7947d40..23348be 100644 --- a/services/payments/src/app/schemas/payment.py +++ b/services/payments/src/app/schemas/payment.py @@ -1,11 +1,14 @@ from __future__ import annotations +from datetime import datetime from typing import Optional from pydantic import BaseModel, ConfigDict +from uuid import UUID +from pydantic import BaseModel, Field class InvoiceCreate(BaseModel): - client_id: str - amount: float - currency: str = "USD" + client_id: UUID + amount: int = Field(ge=0) + currency: str description: Optional[str] = None class InvoiceUpdate(BaseModel): @@ -15,10 +18,23 @@ class InvoiceUpdate(BaseModel): status: Optional[str] = None class InvoiceRead(BaseModel): - id: str - client_id: str + id: UUID + client_id: UUID 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: datetime | None = None + updated_at: datetime | None = None + + class Config: + from_attributes = True \ No newline at end of file diff --git a/services/payments/src/app/schemas/payment.py.bak.1754729104 b/services/payments/src/app/schemas/payment.py.bak.1754729104 new file mode 100644 index 0000000..f8e20ef --- /dev/null +++ b/services/payments/src/app/schemas/payment.py.bak.1754729104 @@ -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 \ No newline at end of file