init commit

This commit is contained in:
2025-09-25 08:05:25 +09:00
commit 4d7551d4f1
56 changed files with 5977 additions and 0 deletions

View File

@@ -0,0 +1,295 @@
from fastapi import FastAPI, HTTPException, Request, Depends
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
import httpx
import time
from typing import Dict
from shared.config import settings
import asyncio
app = FastAPI(title="API Gateway", version="1.0.0")
# CORS middleware
app.add_middleware(
CORSMiddleware,
allow_origins=settings.CORS_ORIGINS,
allow_credentials=True,
allow_methods=["*"],
allow_headers=["*"],
)
# Service registry
SERVICES = {
"users": "http://localhost:8001",
"emergency": "http://localhost:8002",
"location": "http://localhost:8003",
"calendar": "http://localhost:8004",
"notifications": "http://localhost:8005"
}
# Rate limiting (simple in-memory implementation)
request_counts: Dict[str, Dict[str, int]] = {}
RATE_LIMIT_REQUESTS = 100 # requests per minute
RATE_LIMIT_WINDOW = 60 # seconds
def get_client_ip(request: Request) -> str:
"""Get client IP address"""
x_forwarded_for = request.headers.get("X-Forwarded-For")
if x_forwarded_for:
return x_forwarded_for.split(",")[0].strip()
return request.client.host
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()
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
return False
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"]
}
async with httpx.AsyncClient(timeout=30.0) as client:
try:
response = await client.request(
method=method,
url=url,
headers=filtered_headers,
content=body,
params=params
)
return response
except httpx.TimeoutException:
raise HTTPException(status_code=504, detail="Service timeout")
except httpx.ConnectError:
raise HTTPException(status_code=503, detail="Service unavailable")
except Exception as e:
raise HTTPException(status_code=500, detail=f"Proxy error: {str(e)}")
@app.middleware("http")
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 await call_next(request)
# User Service routes
@app.api_route("/api/v1/register", methods=["POST"])
@app.api_route("/api/v1/login", methods=["POST"])
@app.api_route("/api/v1/profile", methods=["GET", "PUT"])
async def user_service_proxy(request: Request):
"""Proxy requests to User Service"""
body = await request.body()
response = await proxy_request(
SERVICES["users"],
request.url.path,
request.method,
dict(request.headers),
body,
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"]}
)
# Emergency Service routes
@app.api_route("/api/v1/alert", methods=["POST"])
@app.api_route("/api/v1/alert/{alert_id}/respond", methods=["POST"])
@app.api_route("/api/v1/alert/{alert_id}/resolve", methods=["PUT"])
@app.api_route("/api/v1/alerts/my", methods=["GET"])
@app.api_route("/api/v1/alerts/active", methods=["GET"])
async def emergency_service_proxy(request: Request):
"""Proxy requests to Emergency Service"""
body = await request.body()
response = await proxy_request(
SERVICES["emergency"],
request.url.path,
request.method,
dict(request.headers),
body,
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"]}
)
# Location Service routes
@app.api_route("/api/v1/update-location", methods=["POST"])
@app.api_route("/api/v1/user-location/{user_id}", methods=["GET"])
@app.api_route("/api/v1/nearby-users", methods=["GET"])
@app.api_route("/api/v1/location-history", methods=["GET"])
@app.api_route("/api/v1/location", methods=["DELETE"])
async def location_service_proxy(request: Request):
"""Proxy requests to Location Service"""
body = await request.body()
response = await proxy_request(
SERVICES["location"],
request.url.path,
request.method,
dict(request.headers),
body,
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"]}
)
# Calendar Service routes
@app.api_route("/api/v1/entries", methods=["GET", "POST"])
@app.api_route("/api/v1/entries/{entry_id}", methods=["DELETE"])
@app.api_route("/api/v1/cycle-overview", methods=["GET"])
@app.api_route("/api/v1/insights", methods=["GET"])
async def calendar_service_proxy(request: Request):
"""Proxy requests to Calendar Service"""
body = await request.body()
response = await proxy_request(
SERVICES["calendar"],
request.url.path,
request.method,
dict(request.headers),
body,
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"]}
)
# Notification Service routes
@app.api_route("/api/v1/register-device", methods=["POST"])
@app.api_route("/api/v1/send-notification", methods=["POST"])
@app.api_route("/api/v1/device-token", methods=["DELETE"])
@app.api_route("/api/v1/my-devices", methods=["GET"])
async def notification_service_proxy(request: Request):
"""Proxy requests to Notification Service"""
body = await request.body()
response = await proxy_request(
SERVICES["notifications"],
request.url.path,
request.method,
dict(request.headers),
body,
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"]}
)
@app.get("/api/v1/health")
async def gateway_health_check():
"""Gateway health check"""
return {"status": "healthy", "service": "api-gateway"}
@app.get("/api/v1/services-status")
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:
response = await client.get(f"{url}/api/v1/health")
service_status[name] = {
"status": "healthy" if response.status_code == 200 else "unhealthy",
"response_time_ms": response.elapsed.total_seconds() * 1000,
"url": url
}
except Exception as e:
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())
return {
"gateway_status": "healthy",
"all_services_healthy": all_healthy,
"services": service_status
}
@app.get("/")
async def root():
"""Root endpoint with API information"""
return {
"service": "Women Safety App API Gateway",
"version": "1.0.0",
"status": "running",
"endpoints": {
"auth": "/api/v1/register, /api/v1/login",
"profile": "/api/v1/profile",
"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"
},
"docs": "/docs"
}
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)