This commit is contained in:
@@ -185,9 +185,70 @@ async def rate_limiting_middleware(request: Request, call_next):
|
|||||||
return await call_next(request)
|
return await call_next(request)
|
||||||
|
|
||||||
|
|
||||||
|
# Authentication routes
|
||||||
|
@app.post("/api/v1/auth/register", response_model=UserResponse, tags=["Authentication"], summary="Register a new user")
|
||||||
|
async def register_user(user_create: UserCreate, request: Request):
|
||||||
|
"""Register a new user"""
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
try:
|
||||||
|
response = await client.post(
|
||||||
|
f"{SERVICES['users']}/api/v1/auth/register",
|
||||||
|
json=user_create.model_dump(),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
error_detail = response.text
|
||||||
|
try:
|
||||||
|
error_json = response.json()
|
||||||
|
error_detail = error_json.get("detail", error_detail)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=error_detail)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Registration error: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/auth/login", response_model=Token, tags=["Authentication"], summary="Login user")
|
||||||
|
async def login_user(user_login: UserLogin, request: Request):
|
||||||
|
"""Login user"""
|
||||||
|
async with httpx.AsyncClient(timeout=30.0) as client:
|
||||||
|
try:
|
||||||
|
response = await client.post(
|
||||||
|
f"{SERVICES['users']}/api/v1/auth/login",
|
||||||
|
json=user_login.model_dump(),
|
||||||
|
headers={
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Accept": "application/json"
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
error_detail = response.text
|
||||||
|
try:
|
||||||
|
error_json = response.json()
|
||||||
|
error_detail = error_json.get("detail", error_detail)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
raise HTTPException(status_code=response.status_code, detail=error_detail)
|
||||||
|
|
||||||
|
except HTTPException:
|
||||||
|
raise
|
||||||
|
except Exception as e:
|
||||||
|
raise HTTPException(status_code=500, detail=f"Login error: {str(e)}")
|
||||||
|
|
||||||
|
|
||||||
# User Service routes
|
# User Service routes
|
||||||
@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.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.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.patch("/api/v1/users/me", operation_id="user_me_patch", response_model=UserResponse, tags=["Users"], summary="Update user profile")
|
||||||
|
|||||||
@@ -1,10 +1,11 @@
|
|||||||
import asyncio
|
import asyncio
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from typing import List
|
||||||
|
|
||||||
import httpx
|
import httpx
|
||||||
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
|
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from sqlalchemy import func, select
|
from sqlalchemy import func, select, update
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from services.emergency_service.models import EmergencyAlert, EmergencyResponse
|
from services.emergency_service.models import EmergencyAlert, EmergencyResponse
|
||||||
@@ -18,7 +19,7 @@ from services.emergency_service.schemas import (
|
|||||||
from services.user_service.models import User
|
from services.user_service.models import User
|
||||||
from shared.auth import get_current_user_from_token
|
from shared.auth import get_current_user_from_token
|
||||||
from shared.config import settings
|
from shared.config import settings
|
||||||
from shared.database import AsyncSessionLocal, get_db
|
from shared.database import get_db
|
||||||
|
|
||||||
app = FastAPI(title="Emergency Service", version="1.0.0")
|
app = FastAPI(title="Emergency Service", version="1.0.0")
|
||||||
|
|
||||||
@@ -55,7 +56,7 @@ async def health_check():
|
|||||||
|
|
||||||
async def get_nearby_users(
|
async def get_nearby_users(
|
||||||
latitude: float, longitude: float, radius_km: float = 1.0
|
latitude: float, longitude: float, radius_km: float = 1.0
|
||||||
) -> list:
|
) -> List[dict]:
|
||||||
"""Get users within radius using Location Service"""
|
"""Get users within radius using Location Service"""
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
@@ -75,7 +76,7 @@ async def get_nearby_users(
|
|||||||
return []
|
return []
|
||||||
|
|
||||||
|
|
||||||
async def send_emergency_notifications(alert_id: int, nearby_users: list):
|
async def send_emergency_notifications(alert_id: int, nearby_users: List[dict]):
|
||||||
"""Send push notifications to nearby users"""
|
"""Send push notifications to nearby users"""
|
||||||
async with httpx.AsyncClient() as client:
|
async with httpx.AsyncClient() as client:
|
||||||
try:
|
try:
|
||||||
@@ -98,15 +99,14 @@ async def create_emergency_alert(
|
|||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Create new emergency alert and notify nearby users"""
|
"""Create new emergency alert"""
|
||||||
|
|
||||||
# Create alert
|
# Create alert
|
||||||
db_alert = EmergencyAlert(
|
db_alert = EmergencyAlert(
|
||||||
user_id=current_user.id,
|
user_id=current_user.id,
|
||||||
latitude=alert_data.latitude,
|
latitude=alert_data.latitude,
|
||||||
longitude=alert_data.longitude,
|
longitude=alert_data.longitude,
|
||||||
address=alert_data.address,
|
address=alert_data.address,
|
||||||
alert_type=alert_data.alert_type.value,
|
alert_type=alert_data.alert_type,
|
||||||
message=alert_data.message,
|
message=alert_data.message,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -114,34 +114,45 @@ async def create_emergency_alert(
|
|||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(db_alert)
|
await db.refresh(db_alert)
|
||||||
|
|
||||||
# Get nearby users and send notifications in background
|
# Process alert in background
|
||||||
background_tasks.add_task(
|
background_tasks.add_task(
|
||||||
process_emergency_alert, db_alert.id, alert_data.latitude, alert_data.longitude
|
process_emergency_alert_in_background,
|
||||||
|
int(db_alert.id), # Convert to int explicitly
|
||||||
|
alert_data.latitude,
|
||||||
|
alert_data.longitude
|
||||||
)
|
)
|
||||||
|
|
||||||
return EmergencyAlertResponse.model_validate(db_alert)
|
return EmergencyAlertResponse.model_validate(db_alert)
|
||||||
|
|
||||||
|
|
||||||
async def process_emergency_alert(alert_id: int, latitude: float, longitude: float):
|
async def process_emergency_alert_in_background(alert_id: int, latitude: float, longitude: float):
|
||||||
"""Process emergency alert - get nearby users and send notifications"""
|
"""Process emergency alert - notify nearby users"""
|
||||||
# Get nearby users
|
try:
|
||||||
nearby_users = await get_nearby_users(
|
# Get nearby users
|
||||||
latitude, longitude, settings.MAX_EMERGENCY_RADIUS_KM
|
nearby_users = await get_nearby_users(latitude, longitude)
|
||||||
)
|
|
||||||
|
|
||||||
# Update alert with notified users count
|
if nearby_users:
|
||||||
async with AsyncSessionLocal() as db:
|
# Create new database session for background task
|
||||||
result = await db.execute(
|
from shared.database import AsyncSessionLocal
|
||||||
select(EmergencyAlert).filter(EmergencyAlert.id == alert_id)
|
async with AsyncSessionLocal() as db:
|
||||||
)
|
try:
|
||||||
alert = result.scalars().first()
|
# Update alert with notification count
|
||||||
if alert:
|
await db.execute(
|
||||||
alert.notified_users_count = len(nearby_users)
|
update(EmergencyAlert)
|
||||||
await db.commit()
|
.where(EmergencyAlert.id == alert_id)
|
||||||
|
.values(notified_users_count=len(nearby_users))
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
# Send notifications
|
# Send notifications
|
||||||
if nearby_users:
|
await send_emergency_notifications(alert_id, nearby_users)
|
||||||
await send_emergency_notifications(alert_id, nearby_users)
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing emergency alert: {e}")
|
||||||
|
await db.rollback()
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in process_emergency_alert_in_background: {e}")
|
||||||
|
|
||||||
|
|
||||||
@app.post("/api/v1/alert/{alert_id}/respond", response_model=EmergencyResponseResponse)
|
@app.post("/api/v1/alert/{alert_id}/respond", response_model=EmergencyResponseResponse)
|
||||||
@@ -152,44 +163,46 @@ async def respond_to_alert(
|
|||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Respond to emergency alert"""
|
"""Respond to emergency alert"""
|
||||||
|
|
||||||
# Check if alert exists
|
# Check if alert exists
|
||||||
result = await db.execute(
|
result = await db.execute(select(EmergencyAlert).filter(EmergencyAlert.id == alert_id))
|
||||||
select(EmergencyAlert).filter(EmergencyAlert.id == alert_id)
|
|
||||||
)
|
|
||||||
alert = result.scalars().first()
|
alert = result.scalars().first()
|
||||||
|
|
||||||
if not alert:
|
if not alert:
|
||||||
raise HTTPException(status_code=404, detail="Alert not found")
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Alert not found"
|
||||||
|
)
|
||||||
|
|
||||||
if alert.is_resolved:
|
# Check if already responded
|
||||||
raise HTTPException(status_code=400, detail="Alert already resolved")
|
|
||||||
|
|
||||||
# Check if user already responded
|
|
||||||
existing_response = await db.execute(
|
existing_response = await db.execute(
|
||||||
select(EmergencyResponse).filter(
|
select(EmergencyResponse).filter(
|
||||||
EmergencyResponse.alert_id == alert_id,
|
EmergencyResponse.alert_id == alert_id,
|
||||||
EmergencyResponse.responder_id == current_user.id,
|
EmergencyResponse.user_id == current_user.id
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if existing_response.scalars().first():
|
if existing_response.scalars().first():
|
||||||
raise HTTPException(
|
raise HTTPException(
|
||||||
status_code=400, detail="You already responded to this alert"
|
status_code=status.HTTP_400_BAD_REQUEST,
|
||||||
|
detail="You have already responded to this alert"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Create response
|
# Create response
|
||||||
db_response = EmergencyResponse(
|
db_response = EmergencyResponse(
|
||||||
alert_id=alert_id,
|
alert_id=alert_id,
|
||||||
responder_id=current_user.id,
|
user_id=current_user.id,
|
||||||
response_type=response_data.response_type.value,
|
response_type=response_data.response_type,
|
||||||
message=response_data.message,
|
message=response_data.message,
|
||||||
eta_minutes=response_data.eta_minutes,
|
eta_minutes=response_data.eta_minutes,
|
||||||
)
|
)
|
||||||
|
|
||||||
db.add(db_response)
|
db.add(db_response)
|
||||||
|
|
||||||
# Update responded users count
|
# Update responded users count
|
||||||
alert.responded_users_count += 1
|
await db.execute(
|
||||||
|
update(EmergencyAlert)
|
||||||
|
.where(EmergencyAlert.id == alert_id)
|
||||||
|
.values(responded_users_count=EmergencyAlert.responded_users_count + 1)
|
||||||
|
)
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(db_response)
|
await db.refresh(db_response)
|
||||||
|
|
||||||
@@ -202,116 +215,110 @@ async def resolve_alert(
|
|||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Mark alert as resolved (only by alert creator)"""
|
"""Resolve emergency alert"""
|
||||||
|
result = await db.execute(select(EmergencyAlert).filter(EmergencyAlert.id == alert_id))
|
||||||
result = await db.execute(
|
|
||||||
select(EmergencyAlert).filter(EmergencyAlert.id == alert_id)
|
|
||||||
)
|
|
||||||
alert = result.scalars().first()
|
alert = result.scalars().first()
|
||||||
|
|
||||||
if not alert:
|
if not alert:
|
||||||
raise HTTPException(status_code=404, detail="Alert not found")
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="Alert not found"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Only alert creator can resolve
|
||||||
if alert.user_id != current_user.id:
|
if alert.user_id != current_user.id:
|
||||||
raise HTTPException(status_code=403, detail="Only alert creator can resolve it")
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_403_FORBIDDEN,
|
||||||
if alert.is_resolved:
|
detail="Only alert creator can resolve the alert"
|
||||||
raise HTTPException(status_code=400, detail="Alert already resolved")
|
)
|
||||||
|
|
||||||
alert.is_resolved = True
|
|
||||||
alert.resolved_at = datetime.utcnow()
|
|
||||||
alert.resolved_by = current_user.id
|
|
||||||
|
|
||||||
|
# Update alert
|
||||||
|
await db.execute(
|
||||||
|
update(EmergencyAlert)
|
||||||
|
.where(EmergencyAlert.id == alert_id)
|
||||||
|
.values(
|
||||||
|
is_resolved=True,
|
||||||
|
resolved_at=datetime.utcnow(),
|
||||||
|
resolved_by=current_user.id
|
||||||
|
)
|
||||||
|
)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
|
|
||||||
return {"message": "Alert resolved successfully"}
|
return {"message": "Alert resolved successfully"}
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/alerts/my", response_model=list[EmergencyAlertResponse])
|
@app.get("/api/v1/alerts/my", response_model=List[EmergencyAlertResponse])
|
||||||
async def get_my_alerts(
|
async def get_my_alerts(
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db)
|
||||||
limit: int = 50,
|
|
||||||
):
|
):
|
||||||
"""Get current user's emergency alerts"""
|
"""Get current user's emergency alerts"""
|
||||||
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(EmergencyAlert)
|
select(EmergencyAlert)
|
||||||
.filter(EmergencyAlert.user_id == current_user.id)
|
.filter(EmergencyAlert.user_id == current_user.id)
|
||||||
.order_by(EmergencyAlert.created_at.desc())
|
.order_by(EmergencyAlert.created_at.desc())
|
||||||
.limit(limit)
|
|
||||||
)
|
)
|
||||||
alerts = result.scalars().all()
|
alerts = result.scalars().all()
|
||||||
|
|
||||||
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/alerts/active", response_model=list[EmergencyAlertResponse])
|
@app.get("/api/v1/alerts/active", response_model=List[EmergencyAlertResponse])
|
||||||
async def get_active_alerts(
|
async def get_active_alerts(
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: User = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db)
|
||||||
limit: int = 20,
|
|
||||||
):
|
):
|
||||||
"""Get active alerts in user's area (last 2 hours)"""
|
"""Get active emergency alerts"""
|
||||||
|
|
||||||
# Get user's current location first
|
|
||||||
async with httpx.AsyncClient() as client:
|
|
||||||
try:
|
|
||||||
response = await client.get(
|
|
||||||
f"http://localhost:8003/api/v1/user-location/{current_user.id}",
|
|
||||||
timeout=5.0,
|
|
||||||
)
|
|
||||||
if response.status_code != 200:
|
|
||||||
raise HTTPException(
|
|
||||||
status_code=400, detail="User location not available"
|
|
||||||
)
|
|
||||||
location_data = response.json()
|
|
||||||
except Exception:
|
|
||||||
raise HTTPException(status_code=400, detail="Location service unavailable")
|
|
||||||
|
|
||||||
# Get alerts from last 2 hours
|
|
||||||
two_hours_ago = datetime.utcnow() - timedelta(hours=2)
|
|
||||||
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(EmergencyAlert)
|
select(EmergencyAlert)
|
||||||
.filter(
|
.filter(EmergencyAlert.is_resolved == False)
|
||||||
EmergencyAlert.is_resolved == False,
|
|
||||||
EmergencyAlert.created_at >= two_hours_ago,
|
|
||||||
)
|
|
||||||
.order_by(EmergencyAlert.created_at.desc())
|
.order_by(EmergencyAlert.created_at.desc())
|
||||||
.limit(limit)
|
.limit(50)
|
||||||
)
|
)
|
||||||
alerts = result.scalars().all()
|
alerts = result.scalars().all()
|
||||||
|
|
||||||
|
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/emergency/reports", response_model=List[EmergencyAlertResponse])
|
||||||
|
async def get_emergency_reports(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get emergency reports/alerts for admin users or general statistics"""
|
||||||
|
# For now, return all active alerts (in production, add admin check)
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert)
|
||||||
|
.filter(EmergencyAlert.is_resolved == False)
|
||||||
|
.order_by(EmergencyAlert.created_at.desc())
|
||||||
|
.limit(50)
|
||||||
|
)
|
||||||
|
alerts = result.scalars().all()
|
||||||
|
|
||||||
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/stats", response_model=EmergencyStats)
|
@app.get("/api/v1/stats", response_model=EmergencyStats)
|
||||||
async def get_emergency_stats(
|
async def get_emergency_stats(
|
||||||
current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""Get emergency service statistics"""
|
"""Get emergency statistics"""
|
||||||
|
# Get total alerts count
|
||||||
# Get total alerts
|
total_alerts_result = await db.execute(select(func.count(EmergencyAlert.id)))
|
||||||
total_result = await db.execute(select(func.count(EmergencyAlert.id)))
|
total_alerts = total_alerts_result.scalar() or 0
|
||||||
total_alerts = total_result.scalar()
|
|
||||||
|
# Get active alerts count
|
||||||
# Get active alerts
|
active_alerts_result = await db.execute(
|
||||||
active_result = await db.execute(
|
select(func.count(EmergencyAlert.id)).filter(EmergencyAlert.is_resolved == False)
|
||||||
select(func.count(EmergencyAlert.id)).filter(
|
|
||||||
EmergencyAlert.is_resolved == False
|
|
||||||
)
|
|
||||||
)
|
)
|
||||||
active_alerts = active_result.scalar()
|
active_alerts = active_alerts_result.scalar() or 0
|
||||||
|
|
||||||
# Get resolved alerts
|
# Calculate resolved alerts
|
||||||
resolved_alerts = total_alerts - active_alerts
|
resolved_alerts = total_alerts - active_alerts
|
||||||
|
|
||||||
# Get total responders
|
# Get total responders count
|
||||||
responders_result = await db.execute(
|
total_responders_result = await db.execute(select(func.count(EmergencyResponse.id)))
|
||||||
select(func.count(func.distinct(EmergencyResponse.responder_id)))
|
total_responders = total_responders_result.scalar() or 0
|
||||||
)
|
|
||||||
total_responders = responders_result.scalar()
|
|
||||||
|
|
||||||
return EmergencyStats(
|
return EmergencyStats(
|
||||||
total_alerts=total_alerts,
|
total_alerts=total_alerts,
|
||||||
@@ -322,13 +329,6 @@ async def get_emergency_stats(
|
|||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@app.get("/api/v1/health")
|
|
||||||
async def health_check():
|
|
||||||
"""Health check endpoint"""
|
|
||||||
return {"status": "healthy", "service": "emergency-service"}
|
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8002)
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8002)
|
|
||||||
379
services/emergency_service/main_old.py
Normal file
379
services/emergency_service/main_old.py
Normal file
@@ -0,0 +1,379 @@
|
|||||||
|
import asyncio
|
||||||
|
from datetime import datetime, timedelta
|
||||||
|
from typing import List
|
||||||
|
|
||||||
|
import httpx
|
||||||
|
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
|
||||||
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
|
from sqlalchemy import func, select, update
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from services.emergency_service.models import EmergencyAlert, EmergencyResponse
|
||||||
|
from services.emergency_service.schemas import (
|
||||||
|
EmergencyAlertCreate,
|
||||||
|
EmergencyAlertResponse,
|
||||||
|
EmergencyResponseCreate,
|
||||||
|
EmergencyResponseResponse,
|
||||||
|
EmergencyStats,
|
||||||
|
)
|
||||||
|
from services.user_service.models import User
|
||||||
|
from shared.auth import get_current_user_from_token
|
||||||
|
from shared.config import settings
|
||||||
|
from shared.database import get_db
|
||||||
|
|
||||||
|
app = FastAPI(title="Emergency Service", version="1.0.0")
|
||||||
|
|
||||||
|
# CORS middleware
|
||||||
|
app.add_middleware(
|
||||||
|
CORSMiddleware,
|
||||||
|
allow_origins=settings.CORS_ORIGINS,
|
||||||
|
allow_credentials=True,
|
||||||
|
allow_methods=["*"],
|
||||||
|
allow_headers=["*"],
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
async def get_current_user(
|
||||||
|
user_data: dict = Depends(get_current_user_from_token),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Get current user from token via auth dependency."""
|
||||||
|
# Get full user object from database
|
||||||
|
result = await db.execute(select(User).filter(User.id == user_data["user_id"]))
|
||||||
|
user = result.scalars().first()
|
||||||
|
if user is None:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
|
||||||
|
)
|
||||||
|
return user
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return {"status": "healthy", "service": "emergency_service"}
|
||||||
|
|
||||||
|
|
||||||
|
async def get_nearby_users(
|
||||||
|
latitude: float, longitude: float, radius_km: float = 1.0
|
||||||
|
) -> List[dict]:
|
||||||
|
"""Get users within radius using Location Service"""
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
response = await client.get(
|
||||||
|
f"http://localhost:8003/api/v1/nearby-users",
|
||||||
|
params={
|
||||||
|
"latitude": latitude,
|
||||||
|
"longitude": longitude,
|
||||||
|
"radius_km": radius_km,
|
||||||
|
},
|
||||||
|
timeout=5.0,
|
||||||
|
)
|
||||||
|
if response.status_code == 200:
|
||||||
|
return response.json()
|
||||||
|
return []
|
||||||
|
except Exception:
|
||||||
|
return []
|
||||||
|
|
||||||
|
|
||||||
|
async def send_emergency_notifications(alert_id: int, nearby_users: List[dict]):
|
||||||
|
"""Send push notifications to nearby users"""
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
await client.post(
|
||||||
|
"http://localhost:8005/api/v1/send-emergency-notifications",
|
||||||
|
json={
|
||||||
|
"alert_id": alert_id,
|
||||||
|
"user_ids": [user["user_id"] for user in nearby_users],
|
||||||
|
},
|
||||||
|
timeout=10.0,
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Failed to send notifications: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
async def process_emergency_alert_task(alert_id: int, latitude: float, longitude: float):
|
||||||
|
"""Process emergency alert - notify nearby users"""
|
||||||
|
try:
|
||||||
|
# Get nearby users
|
||||||
|
nearby_users = await get_nearby_users(latitude, longitude)
|
||||||
|
|
||||||
|
if nearby_users:
|
||||||
|
# Update alert with notification count using dependency injection
|
||||||
|
async for db in get_db():
|
||||||
|
try:
|
||||||
|
await db.execute(
|
||||||
|
update(EmergencyAlert)
|
||||||
|
.where(EmergencyAlert.id == alert_id)
|
||||||
|
.values(notified_users_count=len(nearby_users))
|
||||||
|
)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
# Send notifications
|
||||||
|
await send_emergency_notifications(alert_id, nearby_users)
|
||||||
|
break
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error processing emergency alert: {e}")
|
||||||
|
await db.rollback()
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Error in process_emergency_alert_task: {e}")
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/alert", response_model=EmergencyAlertResponse)
|
||||||
|
async def create_emergency_alert(
|
||||||
|
alert_data: EmergencyAlertCreate,
|
||||||
|
background_tasks: BackgroundTasks,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Create new emergency alert"""
|
||||||
|
# Create alert
|
||||||
|
db_alert = EmergencyAlert(
|
||||||
|
user_id=current_user.id,
|
||||||
|
latitude=alert_data.latitude,
|
||||||
|
longitude=alert_data.longitude,
|
||||||
|
address=alert_data.address,
|
||||||
|
alert_type=alert_data.alert_type,
|
||||||
|
message=alert_data.message,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_alert)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(db_alert)
|
||||||
|
|
||||||
|
# Process alert in background
|
||||||
|
background_tasks.add_task(
|
||||||
|
process_emergency_alert_task, db_alert.id, alert_data.latitude, alert_data.longitude
|
||||||
|
)
|
||||||
|
|
||||||
|
return EmergencyAlertResponse.model_validate(db_alert)
|
||||||
|
|
||||||
|
|
||||||
|
async def process_emergency_alert(alert_id: int, latitude: float, longitude: float):
|
||||||
|
"""Process emergency alert - get nearby users and send notifications"""
|
||||||
|
# Get nearby users
|
||||||
|
nearby_users = await get_nearby_users(
|
||||||
|
latitude, longitude, settings.MAX_EMERGENCY_RADIUS_KM
|
||||||
|
)
|
||||||
|
|
||||||
|
# Update alert with notified users count
|
||||||
|
async with AsyncSessionLocal() as db:
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert).filter(EmergencyAlert.id == alert_id)
|
||||||
|
)
|
||||||
|
alert = result.scalars().first()
|
||||||
|
if alert:
|
||||||
|
alert.notified_users_count = len(nearby_users)
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
# Send notifications
|
||||||
|
if nearby_users:
|
||||||
|
await send_emergency_notifications(alert_id, nearby_users)
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/alert/{alert_id}/respond", response_model=EmergencyResponseResponse)
|
||||||
|
async def respond_to_alert(
|
||||||
|
alert_id: int,
|
||||||
|
response_data: EmergencyResponseCreate,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Respond to emergency alert"""
|
||||||
|
|
||||||
|
# Check if alert exists
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert).filter(EmergencyAlert.id == alert_id)
|
||||||
|
)
|
||||||
|
alert = result.scalars().first()
|
||||||
|
if not alert:
|
||||||
|
raise HTTPException(status_code=404, detail="Alert not found")
|
||||||
|
|
||||||
|
if alert.is_resolved:
|
||||||
|
raise HTTPException(status_code=400, detail="Alert already resolved")
|
||||||
|
|
||||||
|
# Check if user already responded
|
||||||
|
existing_response = await db.execute(
|
||||||
|
select(EmergencyResponse).filter(
|
||||||
|
EmergencyResponse.alert_id == alert_id,
|
||||||
|
EmergencyResponse.responder_id == current_user.id,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if existing_response.scalars().first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="You already responded to this alert"
|
||||||
|
)
|
||||||
|
|
||||||
|
# Create response
|
||||||
|
db_response = EmergencyResponse(
|
||||||
|
alert_id=alert_id,
|
||||||
|
responder_id=current_user.id,
|
||||||
|
response_type=response_data.response_type.value,
|
||||||
|
message=response_data.message,
|
||||||
|
eta_minutes=response_data.eta_minutes,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(db_response)
|
||||||
|
|
||||||
|
# Update responded users count
|
||||||
|
alert.responded_users_count += 1
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(db_response)
|
||||||
|
|
||||||
|
return EmergencyResponseResponse.model_validate(db_response)
|
||||||
|
|
||||||
|
|
||||||
|
@app.put("/api/v1/alert/{alert_id}/resolve")
|
||||||
|
async def resolve_alert(
|
||||||
|
alert_id: int,
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Mark alert as resolved (only by alert creator)"""
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert).filter(EmergencyAlert.id == alert_id)
|
||||||
|
)
|
||||||
|
alert = result.scalars().first()
|
||||||
|
|
||||||
|
if not alert:
|
||||||
|
raise HTTPException(status_code=404, detail="Alert not found")
|
||||||
|
|
||||||
|
if alert.user_id != current_user.id:
|
||||||
|
raise HTTPException(status_code=403, detail="Only alert creator can resolve it")
|
||||||
|
|
||||||
|
if alert.is_resolved:
|
||||||
|
raise HTTPException(status_code=400, detail="Alert already resolved")
|
||||||
|
|
||||||
|
alert.is_resolved = True
|
||||||
|
alert.resolved_at = datetime.utcnow()
|
||||||
|
alert.resolved_by = current_user.id
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
|
||||||
|
return {"message": "Alert resolved successfully"}
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/alerts/my", response_model=list[EmergencyAlertResponse])
|
||||||
|
async def get_my_alerts(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
limit: int = 50,
|
||||||
|
):
|
||||||
|
"""Get current user's emergency alerts"""
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert)
|
||||||
|
.filter(EmergencyAlert.user_id == current_user.id)
|
||||||
|
.order_by(EmergencyAlert.created_at.desc())
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
alerts = result.scalars().all()
|
||||||
|
|
||||||
|
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/alerts/active", response_model=list[EmergencyAlertResponse])
|
||||||
|
async def get_active_alerts(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
limit: int = 20,
|
||||||
|
):
|
||||||
|
"""Get active alerts in user's area (last 2 hours)"""
|
||||||
|
|
||||||
|
# Get user's current location first
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
response = await client.get(
|
||||||
|
f"http://localhost:8003/api/v1/user-location/{current_user.id}",
|
||||||
|
timeout=5.0,
|
||||||
|
)
|
||||||
|
if response.status_code != 200:
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="User location not available"
|
||||||
|
)
|
||||||
|
location_data = response.json()
|
||||||
|
except Exception:
|
||||||
|
raise HTTPException(status_code=400, detail="Location service unavailable")
|
||||||
|
|
||||||
|
# Get alerts from last 2 hours
|
||||||
|
two_hours_ago = datetime.utcnow() - timedelta(hours=2)
|
||||||
|
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert)
|
||||||
|
.filter(
|
||||||
|
EmergencyAlert.is_resolved == False,
|
||||||
|
EmergencyAlert.created_at >= two_hours_ago,
|
||||||
|
)
|
||||||
|
.order_by(EmergencyAlert.created_at.desc())
|
||||||
|
.limit(limit)
|
||||||
|
)
|
||||||
|
alerts = result.scalars().all()
|
||||||
|
|
||||||
|
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/stats", response_model=EmergencyStats)
|
||||||
|
async def get_emergency_stats(
|
||||||
|
current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get emergency service statistics"""
|
||||||
|
|
||||||
|
# Get total alerts
|
||||||
|
total_result = await db.execute(select(func.count(EmergencyAlert.id)))
|
||||||
|
total_alerts = total_result.scalar()
|
||||||
|
|
||||||
|
# Get active alerts
|
||||||
|
active_result = await db.execute(
|
||||||
|
select(func.count(EmergencyAlert.id)).filter(
|
||||||
|
EmergencyAlert.is_resolved == False
|
||||||
|
)
|
||||||
|
)
|
||||||
|
active_alerts = active_result.scalar()
|
||||||
|
|
||||||
|
# Get resolved alerts
|
||||||
|
resolved_alerts = total_alerts - active_alerts
|
||||||
|
|
||||||
|
# Get total responders
|
||||||
|
responders_result = await db.execute(
|
||||||
|
select(func.count(func.distinct(EmergencyResponse.responder_id)))
|
||||||
|
)
|
||||||
|
total_responders = responders_result.scalar()
|
||||||
|
|
||||||
|
return EmergencyStats(
|
||||||
|
total_alerts=total_alerts,
|
||||||
|
active_alerts=active_alerts,
|
||||||
|
resolved_alerts=resolved_alerts,
|
||||||
|
avg_response_time_minutes=None, # TODO: Calculate this
|
||||||
|
total_responders=total_responders,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/emergency/reports", response_model=list[EmergencyAlertResponse])
|
||||||
|
async def get_emergency_reports(
|
||||||
|
current_user: User = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db)
|
||||||
|
):
|
||||||
|
"""Get emergency reports/alerts for admin users or general statistics"""
|
||||||
|
# For now, return all active alerts (in production, add admin check)
|
||||||
|
result = await db.execute(
|
||||||
|
select(EmergencyAlert)
|
||||||
|
.filter(EmergencyAlert.is_resolved == False)
|
||||||
|
.order_by(EmergencyAlert.created_at.desc())
|
||||||
|
.limit(50)
|
||||||
|
)
|
||||||
|
alerts = result.scalars().all()
|
||||||
|
|
||||||
|
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
|
||||||
|
|
||||||
|
|
||||||
|
@app.get("/api/v1/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return {"status": "healthy", "service": "emergency-service"}
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
import uvicorn
|
||||||
|
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=8002)
|
||||||
@@ -120,13 +120,20 @@ while true; do
|
|||||||
# Check if services are still running
|
# Check if services are still running
|
||||||
if ! curl -s http://localhost:8000/health > /dev/null 2>&1; then
|
if ! curl -s http://localhost:8000/health > /dev/null 2>&1; then
|
||||||
echo "⚠️ API Gateway seems to be down, restarting..."
|
echo "⚠️ API Gateway seems to be down, restarting..."
|
||||||
cd services/api_gateway && python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload &
|
(cd services/api_gateway && python -m uvicorn main:app --host 0.0.0.0 --port 8000 --reload) &
|
||||||
cd ../..
|
|
||||||
fi
|
fi
|
||||||
|
|
||||||
if ! curl -s http://localhost:8001/health > /dev/null 2>&1; then
|
if ! curl -s http://localhost:8001/health > /dev/null 2>&1; then
|
||||||
echo "⚠️ User Service seems to be down, restarting..."
|
echo "⚠️ User Service seems to be down, restarting..."
|
||||||
cd services/user_service && python -m uvicorn main:app --host 0.0.0.0 --port 8001 --reload &
|
(cd services/user_service && python -m uvicorn main:app --host 0.0.0.0 --port 8001 --reload) &
|
||||||
cd ../..
|
|
||||||
fi
|
fi
|
||||||
done
|
done
|
||||||
|
|
||||||
|
curl -s -X POST "http://localhost:8000/api/v1/auth/register" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"email": "test@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
"first_name": "Test",
|
||||||
|
"last_name": "User"
|
||||||
|
}'
|
||||||
Reference in New Issue
Block a user