fixes
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-09-25 15:32:19 +09:00
parent bd7a481803
commit dd7349bb4c
9 changed files with 646 additions and 80 deletions

View File

@@ -1,15 +1,45 @@
import asyncio
import time
from typing import Dict
from typing import Dict, Any, Optional, List
import httpx
from fastapi import Depends, FastAPI, HTTPException, Request
from fastapi.middleware.cors import CORSMiddleware
from fastapi.responses import JSONResponse
from fastapi.openapi.utils import get_openapi
from pydantic import BaseModel, Field
from shared.config import settings
from services.user_service.schemas import UserCreate, UserLogin, UserResponse, UserUpdate, Token
app = FastAPI(title="API Gateway", version="1.0.0")
# Импортируем схемы для экстренных контактов
class EmergencyContactBase(BaseModel):
name: str = Field(..., min_length=1, max_length=100)
phone_number: str = Field(..., min_length=5, max_length=20)
relationship: Optional[str] = Field(None, max_length=50)
notes: Optional[str] = Field(None, max_length=500)
class EmergencyContactCreate(EmergencyContactBase):
pass
class EmergencyContactUpdate(BaseModel):
name: Optional[str] = Field(None, min_length=1, max_length=100)
phone_number: Optional[str] = Field(None, min_length=5, max_length=20)
relationship: Optional[str] = Field(None, max_length=50)
notes: Optional[str] = Field(None, max_length=500)
class EmergencyContactResponse(EmergencyContactBase):
id: int
uuid: str
user_id: int
class Config:
from_attributes = True
app = FastAPI(title="API Gateway", version="1.0.0", openapi_url="/api/openapi.json")
# CORS middleware
app.add_middleware(
@@ -40,7 +70,7 @@ def get_client_ip(request: Request) -> str:
x_forwarded_for = request.headers.get("X-Forwarded-For")
if x_forwarded_for:
return x_forwarded_for.split(",")[0].strip()
return request.client.host
return request.client.host if request.client else "127.0.0.1"
def is_rate_limited(client_ip: str) -> bool:
@@ -78,12 +108,23 @@ async def proxy_request(
path: str,
method: str,
headers: dict,
body: bytes = None,
params: dict = None,
body: Optional[bytes] = None,
params: Optional[dict] = None,
user_create: Optional[UserCreate] = None,
user_update: Optional[UserUpdate] = None,
user_login: Optional[UserLogin] = None,
emergency_contact_create: Optional[EmergencyContactCreate] = None,
emergency_contact_update: Optional[EmergencyContactUpdate] = None
):
"""Proxy request to microservice"""
url = f"{service_url}{path}"
# Для отладки
print(f"Proxy request to: {url}, method: {method}")
if body:
print(f"Request body: {body.decode('utf-8')[:100]}...")
# Remove hop-by-hop headers
filtered_headers = {
k: v
@@ -111,12 +152,21 @@ async def proxy_request(
content=body,
params=params,
)
# Для отладки
print(f"Response status: {response.status_code}")
if response.status_code >= 400:
print(f"Error response: {response.text}")
return response
except httpx.TimeoutException:
print(f"Timeout error for {url}")
raise HTTPException(status_code=504, detail="Service timeout")
except httpx.ConnectError:
print(f"Connection error for {url}")
raise HTTPException(status_code=503, detail="Service unavailable")
except Exception as e:
print(f"Proxy error for {url}: {str(e)}")
raise HTTPException(status_code=500, detail=f"Proxy error: {str(e)}")
@@ -136,37 +186,88 @@ async def rate_limiting_middleware(request: Request, call_next):
# 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):
@app.post("/api/v1/auth/register", operation_id="user_auth_register", response_model=UserResponse, tags=["Authentication"], summary="Register a new user")
@app.post("/api/v1/auth/login", operation_id="user_auth_login", response_model=Token, tags=["Authentication"], summary="Login user")
@app.post("/api/v1/users/register", operation_id="user_register", response_model=UserResponse, tags=["Users"], summary="Register a new user")
@app.get("/api/v1/users/me", operation_id="user_me_get", response_model=UserResponse, tags=["Users"], summary="Get current user profile")
@app.patch("/api/v1/users/me", operation_id="user_me_patch", response_model=UserResponse, tags=["Users"], summary="Update user profile")
@app.put("/api/v1/users/me", operation_id="user_me_put", response_model=UserResponse, tags=["Users"], summary="Update user profile")
@app.get("/api/v1/users/me/emergency-contacts", operation_id="user_emergency_contacts_get", response_model=List[EmergencyContactResponse], tags=["Emergency Contacts"], summary="Get all emergency contacts")
@app.post("/api/v1/users/me/emergency-contacts", operation_id="user_emergency_contacts_post", response_model=EmergencyContactResponse, tags=["Emergency Contacts"], summary="Create a new emergency contact")
@app.get("/api/v1/users/me/emergency-contacts/{contact_id}", operation_id="user_emergency_contact_get", response_model=EmergencyContactResponse, tags=["Emergency Contacts"], summary="Get emergency contact by ID")
@app.delete("/api/v1/users/me/emergency-contacts/{contact_id}", operation_id="user_emergency_contact_delete", tags=["Emergency Contacts"], summary="Delete emergency contact")
@app.patch("/api/v1/users/me/emergency-contacts/{contact_id}", operation_id="user_emergency_contact_patch", response_model=EmergencyContactResponse, tags=["Emergency Contacts"], summary="Update emergency contact")
@app.get("/api/v1/users/dashboard", operation_id="user_dashboard_get", tags=["Users"])
@app.post("/api/v1/users/me/change-password", operation_id="user_change_password", tags=["Users"])
@app.get("/api/v1/profile", operation_id="user_profile_get", response_model=UserResponse, tags=["Users"])
@app.put("/api/v1/profile", operation_id="user_profile_update", response_model=UserResponse, tags=["Users"])
async def user_service_proxy(
request: Request,
user_create: Optional[UserCreate] = None,
user_login: Optional[UserLogin] = None,
user_update: Optional[UserUpdate] = None,
emergency_contact_create: Optional[EmergencyContactCreate] = None,
emergency_contact_update: Optional[EmergencyContactUpdate] = None
):
"""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"]
},
)
print(f"User service proxy: {request.url.path}, method: {request.method}")
try:
response = await proxy_request(
SERVICES["users"],
request.url.path,
request.method,
dict(request.headers),
body,
dict(request.query_params),
user_create=user_create,
user_login=user_login,
user_update=user_update,
emergency_contact_create=emergency_contact_create,
emergency_contact_update=emergency_contact_update
)
try:
response_json = response.json()
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"]
},
)
except Exception as e:
print(f"Error processing JSON response: {str(e)}")
print(f"Response text: {response.text}")
return JSONResponse(
status_code=500,
content={"detail": f"Error processing response: {str(e)}"},
)
except Exception as e:
print(f"Error in user_service_proxy: {str(e)}")
return JSONResponse(
status_code=500,
content={"detail": f"Proxy error: {str(e)}"},
)
# 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"])
@app.api_route("/api/v1/emergency/alerts", methods=["POST"], operation_id="emergency_alerts_post")
@app.api_route("/api/v1/emergency/alerts", methods=["GET"], operation_id="emergency_alerts_get")
@app.api_route("/api/v1/emergency/alerts/my", methods=["GET"], operation_id="emergency_alerts_my_get")
@app.api_route("/api/v1/emergency/alerts/nearby", methods=["GET"], operation_id="emergency_alerts_nearby_get")
@app.api_route("/api/v1/emergency/alerts/{alert_id}", methods=["GET"], operation_id="emergency_alert_get")
@app.api_route("/api/v1/emergency/alerts/{alert_id}", methods=["PATCH"], operation_id="emergency_alert_patch")
@app.api_route("/api/v1/emergency/alerts/{alert_id}", methods=["DELETE"], operation_id="emergency_alert_delete")
@app.api_route("/api/v1/emergency/alerts/{alert_id}/cancel", methods=["PATCH"], operation_id="emergency_alert_cancel")
@app.api_route("/api/v1/emergency/reports", methods=["POST"], operation_id="emergency_reports_post")
@app.api_route("/api/v1/emergency/reports", methods=["GET"], operation_id="emergency_reports_get")
@app.api_route("/api/v1/emergency/reports/nearby", methods=["GET"], operation_id="emergency_reports_nearby_get")
@app.api_route("/api/v1/emergency/reports/{report_id}", methods=["GET"], operation_id="emergency_report_get")
@app.api_route("/api/v1/emergency/reports/{report_id}", methods=["PATCH"], operation_id="emergency_report_patch")
@app.api_route("/api/v1/emergency/reports/{report_id}", methods=["DELETE"], operation_id="emergency_report_delete")
async def emergency_service_proxy(request: Request):
"""Proxy requests to Emergency Service"""
body = await request.body()
@@ -190,11 +291,15 @@ async def emergency_service_proxy(request: Request):
# 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"])
@app.api_route("/api/v1/locations/update", methods=["POST"], operation_id="location_update_post")
@app.api_route("/api/v1/locations/last", methods=["GET"], operation_id="location_last_get")
@app.api_route("/api/v1/locations/history", methods=["GET"], operation_id="location_history_get")
@app.api_route("/api/v1/locations/users/nearby", methods=["GET"], operation_id="location_users_nearby_get")
@app.api_route("/api/v1/locations/safe-places", methods=["GET"], operation_id="location_safe_places_get")
@app.api_route("/api/v1/locations/safe-places", methods=["POST"], operation_id="location_safe_places_post")
@app.api_route("/api/v1/locations/safe-places/{place_id}", methods=["GET"], operation_id="location_safe_place_get")
@app.api_route("/api/v1/locations/safe-places/{place_id}", methods=["PATCH"], operation_id="location_safe_place_patch")
@app.api_route("/api/v1/locations/safe-places/{place_id}", methods=["DELETE"], operation_id="location_safe_place_delete")
async def location_service_proxy(request: Request):
"""Proxy requests to Location Service"""
body = await request.body()
@@ -218,10 +323,17 @@ async def location_service_proxy(request: Request):
# 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"])
@app.api_route("/api/v1/calendar/entries", methods=["GET"], operation_id="calendar_entries_get")
@app.api_route("/api/v1/calendar/entries", methods=["POST"], operation_id="calendar_entries_post")
@app.api_route("/api/v1/calendar/entries/{entry_id}", methods=["GET"], operation_id="calendar_entry_get")
@app.api_route("/api/v1/calendar/entries/{entry_id}", methods=["PUT"], operation_id="calendar_entry_put")
@app.api_route("/api/v1/calendar/entries/{entry_id}", methods=["DELETE"], operation_id="calendar_entry_delete")
@app.api_route("/api/v1/calendar/cycle-overview", methods=["GET"], operation_id="calendar_cycle_overview_get")
@app.api_route("/api/v1/calendar/insights", methods=["GET"], operation_id="calendar_insights_get")
@app.api_route("/api/v1/calendar/reminders", methods=["GET"], operation_id="calendar_reminders_get")
@app.api_route("/api/v1/calendar/reminders", methods=["POST"], operation_id="calendar_reminders_post")
@app.api_route("/api/v1/calendar/settings", methods=["GET"], operation_id="calendar_settings_get")
@app.api_route("/api/v1/calendar/settings", methods=["PUT"], operation_id="calendar_settings_put")
async def calendar_service_proxy(request: Request):
"""Proxy requests to Calendar Service"""
body = await request.body()
@@ -245,10 +357,14 @@ async def calendar_service_proxy(request: Request):
# 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"])
@app.api_route("/api/v1/notifications/devices", methods=["GET"], operation_id="notifications_devices_get")
@app.api_route("/api/v1/notifications/devices", methods=["POST"], operation_id="notifications_devices_post")
@app.api_route("/api/v1/notifications/devices/{device_id}", methods=["DELETE"], operation_id="notifications_device_delete")
@app.api_route("/api/v1/notifications/devices/{device_id}", methods=["GET"], operation_id="notifications_device_get")
@app.api_route("/api/v1/notifications/preferences", methods=["GET"], operation_id="notifications_preferences_get")
@app.api_route("/api/v1/notifications/preferences", methods=["POST"], operation_id="notifications_preferences_post")
@app.api_route("/api/v1/notifications/test", methods=["POST"], operation_id="notifications_test_post")
@app.api_route("/api/v1/notifications/history", methods=["GET"], operation_id="notifications_history_get")
async def notification_service_proxy(request: Request):
"""Proxy requests to Notification Service"""
body = await request.body()
@@ -317,17 +433,59 @@ async def root():
"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",
"auth": "/api/v1/auth/register, /api/v1/auth/login",
"users": "/api/v1/users/me, /api/v1/users/dashboard",
"emergency": "/api/v1/emergency/alerts, /api/v1/emergency/reports",
"location": "/api/v1/locations/update, /api/v1/locations/safe-places",
"calendar": "/api/v1/calendar/entries, /api/v1/calendar/cycle-overview",
"notifications": "/api/v1/notifications/devices, /api/v1/notifications/history",
},
"docs": "/docs",
}
# Переопределение схемы OpenAPI
def custom_openapi():
if app.openapi_schema:
return app.openapi_schema
openapi_schema = get_openapi(
title="Women's Safety App API Gateway",
version="1.0.0",
description="API Gateway для Women's Safety App с поддержкой микросервисной архитектуры",
routes=app.routes,
)
# Добавление примеров запросов для маршрутов
if "paths" in openapi_schema:
if "/api/v1/auth/register" in openapi_schema["paths"]:
request_example = {
"username": "user123",
"email": "user@example.com",
"password": "Password123!",
"full_name": "John Doe",
"phone_number": "+7123456789"
}
path_item = openapi_schema["paths"]["/api/v1/auth/register"]
for method in path_item:
if "requestBody" in path_item[method]:
path_item[method]["requestBody"]["content"]["application/json"]["example"] = request_example
if "/api/v1/auth/login" in openapi_schema["paths"]:
login_example = {
"username": "user123",
"password": "Password123!"
}
path_item = openapi_schema["paths"]["/api/v1/auth/login"]
for method in path_item:
if "requestBody" in path_item[method]:
path_item[method]["requestBody"]["content"]["application/json"]["example"] = login_example
app.openapi_schema = openapi_schema
return app.openapi_schema
app.openapi = custom_openapi
if __name__ == "__main__":
import uvicorn