pipeline issues fix
Some checks failed
continuous-integration/drone/push Build is failing

This commit is contained in:
2025-09-25 11:59:54 +09:00
parent dc50a9858e
commit 4e3768a6ee
39 changed files with 1297 additions and 739 deletions

View File

@@ -0,0 +1 @@
# API Gateway Package

View File

@@ -1,11 +1,13 @@
from fastapi import FastAPI, HTTPException, Request, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import httpx
import asyncio
import time
from typing import Dict
import httpx
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from shared.config import settings
import asyncio
app = FastAPI(title="API Gateway", version="1.0.0")
@@ -21,10 +23,10 @@ app.add_middleware(
# Service registry
SERVICES = {
"users": "http://localhost:8001",
"emergency": "http://localhost:8002",
"emergency": "http://localhost:8002",
"location": "http://localhost:8003",
"calendar": "http://localhost:8004",
"notifications": "http://localhost:8005"
"notifications": "http://localhost:8005",
}
# Rate limiting (simple in-memory implementation)
@@ -45,40 +47,61 @@ def is_rate_limited(client_ip: str) -> bool:
"""Check if client is rate limited"""
current_time = int(time.time())
window_start = current_time - RATE_LIMIT_WINDOW
if client_ip not in request_counts:
request_counts[client_ip] = {}
# Clean old entries
request_counts[client_ip] = {
timestamp: count for timestamp, count in request_counts[client_ip].items()
timestamp: count
for timestamp, count in request_counts[client_ip].items()
if int(timestamp) > window_start
}
# Count requests in current window
total_requests = sum(request_counts[client_ip].values())
if total_requests >= RATE_LIMIT_REQUESTS:
return True
# Add current request
timestamp_key = str(current_time)
request_counts[client_ip][timestamp_key] = request_counts[client_ip].get(timestamp_key, 0) + 1
request_counts[client_ip][timestamp_key] = (
request_counts[client_ip].get(timestamp_key, 0) + 1
)
return False
async def proxy_request(service_url: str, path: str, method: str, headers: dict, body: bytes = None, params: dict = None):
async def proxy_request(
service_url: str,
path: str,
method: str,
headers: dict,
body: bytes = None,
params: dict = None,
):
"""Proxy request to microservice"""
url = f"{service_url}{path}"
# Remove hop-by-hop headers
filtered_headers = {
k: v for k, v in headers.items()
if k.lower() not in ["host", "connection", "upgrade", "proxy-connection",
"proxy-authenticate", "proxy-authorization", "te", "trailers", "transfer-encoding"]
k: v
for k, v in headers.items()
if k.lower()
not in [
"host",
"connection",
"upgrade",
"proxy-connection",
"proxy-authenticate",
"proxy-authorization",
"te",
"trailers",
"transfer-encoding",
]
}
async with httpx.AsyncClient(timeout=30.0) as client:
try:
response = await client.request(
@@ -86,7 +109,7 @@ async def proxy_request(service_url: str, path: str, method: str, headers: dict,
url=url,
headers=filtered_headers,
content=body,
params=params
params=params,
)
return response
except httpx.TimeoutException:
@@ -101,17 +124,14 @@ async def proxy_request(service_url: str, path: str, method: str, headers: dict,
async def rate_limiting_middleware(request: Request, call_next):
"""Rate limiting middleware"""
client_ip = get_client_ip(request)
# Skip rate limiting for health checks
if request.url.path.endswith("/health"):
return await call_next(request)
if is_rate_limited(client_ip):
return JSONResponse(
status_code=429,
content={"detail": "Rate limit exceeded"}
)
return JSONResponse(status_code=429, content={"detail": "Rate limit exceeded"})
return await call_next(request)
@@ -128,12 +148,16 @@ async def user_service_proxy(request: Request):
request.method,
dict(request.headers),
body,
dict(request.query_params)
dict(request.query_params),
)
return JSONResponse(
status_code=response.status_code,
content=response.json(),
headers={k: v for k, v in response.headers.items() if k.lower() not in ["content-length", "transfer-encoding"]}
headers={
k: v
for k, v in response.headers.items()
if k.lower() not in ["content-length", "transfer-encoding"]
},
)
@@ -152,12 +176,16 @@ async def emergency_service_proxy(request: Request):
request.method,
dict(request.headers),
body,
dict(request.query_params)
dict(request.query_params),
)
return JSONResponse(
status_code=response.status_code,
content=response.json(),
headers={k: v for k, v in response.headers.items() if k.lower() not in ["content-length", "transfer-encoding"]}
headers={
k: v
for k, v in response.headers.items()
if k.lower() not in ["content-length", "transfer-encoding"]
},
)
@@ -176,12 +204,16 @@ async def location_service_proxy(request: Request):
request.method,
dict(request.headers),
body,
dict(request.query_params)
dict(request.query_params),
)
return JSONResponse(
status_code=response.status_code,
content=response.json(),
headers={k: v for k, v in response.headers.items() if k.lower() not in ["content-length", "transfer-encoding"]}
headers={
k: v
for k, v in response.headers.items()
if k.lower() not in ["content-length", "transfer-encoding"]
},
)
@@ -199,12 +231,16 @@ async def calendar_service_proxy(request: Request):
request.method,
dict(request.headers),
body,
dict(request.query_params)
dict(request.query_params),
)
return JSONResponse(
status_code=response.status_code,
content=response.json(),
headers={k: v for k, v in response.headers.items() if k.lower() not in ["content-length", "transfer-encoding"]}
headers={
k: v
for k, v in response.headers.items()
if k.lower() not in ["content-length", "transfer-encoding"]
},
)
@@ -222,12 +258,16 @@ async def notification_service_proxy(request: Request):
request.method,
dict(request.headers),
body,
dict(request.query_params)
dict(request.query_params),
)
return JSONResponse(
status_code=response.status_code,
content=response.json(),
headers={k: v for k, v in response.headers.items() if k.lower() not in ["content-length", "transfer-encoding"]}
headers={
k: v
for k, v in response.headers.items()
if k.lower() not in ["content-length", "transfer-encoding"]
},
)
@@ -241,7 +281,7 @@ async def gateway_health_check():
async def check_services_status():
"""Check status of all microservices"""
service_status = {}
async def check_service(name: str, url: str):
try:
async with httpx.AsyncClient(timeout=5.0) as client:
@@ -249,25 +289,23 @@ async def check_services_status():
service_status[name] = {
"status": "healthy" if response.status_code == 200 else "unhealthy",
"response_time_ms": response.elapsed.total_seconds() * 1000,
"url": url
"url": url,
}
except Exception as e:
service_status[name] = {
"status": "unhealthy",
"error": str(e),
"url": url
}
service_status[name] = {"status": "unhealthy", "error": str(e), "url": url}
# Check all services concurrently
tasks = [check_service(name, url) for name, url in SERVICES.items()]
await asyncio.gather(*tasks)
all_healthy = all(status["status"] == "healthy" for status in service_status.values())
all_healthy = all(
status["status"] == "healthy" for status in service_status.values()
)
return {
"gateway_status": "healthy",
"all_services_healthy": all_healthy,
"services": service_status
"services": service_status,
}
@@ -284,12 +322,13 @@ async def root():
"emergency": "/api/v1/alert, /api/v1/alerts/*",
"location": "/api/v1/update-location, /api/v1/nearby-users",
"calendar": "/api/v1/entries, /api/v1/cycle-overview",
"notifications": "/api/v1/register-device, /api/v1/send-notification"
"notifications": "/api/v1/register-device, /api/v1/send-notification",
},
"docs": "/docs"
"docs": "/docs",
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)
uvicorn.run(app, host="0.0.0.0", port=8000)