main functions commit
All checks were successful
continuous-integration/drone/push Build is passing

This commit is contained in:
2025-10-19 19:50:00 +09:00
parent ce72785184
commit 3050e084fa
39 changed files with 7149 additions and 186 deletions

View File

@@ -1,11 +1,14 @@
import asyncio
import json
import logging
from datetime import datetime, timedelta
from typing import List, Optional
from typing import List, Optional, Dict, Set
import math
import httpx
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, Query, status
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, Query, status, WebSocket, WebSocketDisconnect
from fastapi.middleware.cors import CORSMiddleware
from fastapi.security import HTTPBearer, HTTPAuthorizationCredentials
from sqlalchemy import func, select, update, desc, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession
@@ -24,18 +27,11 @@ from services.emergency_service.schemas import (
NearbyAlertResponse,
SafetyCheckCreate,
SafetyCheckResponse,
EmergencyEventDetails,
UserInfo,
)
# Упрощенная модель User для Emergency Service
from sqlalchemy import Column, Integer, String, Boolean
from shared.database import BaseModel
class User(BaseModel):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
is_active = Column(Boolean, default=True)
# Import User model from user_service
from services.user_service.models import User
from shared.auth import get_current_user_from_token
from shared.config import settings
@@ -52,7 +48,40 @@ async def get_db():
finally:
await session.close()
app = FastAPI(title="Emergency Service", version="1.0.0")
app = FastAPI(
title="Emergency Service",
version="1.0.0",
description="""
Emergency Service API для системы безопасности женщин.
## Авторизация
Все эндпоинты требуют Bearer токен в заголовке Authorization.
Получить токен можно через User Service:
```
POST /api/v1/auth/login
```
Использование токена:
```
Authorization: Bearer <your_jwt_token>
```
""",
contact={
"name": "Women's Safety App Team",
"url": "https://example.com/support",
"email": "support@example.com",
},
)
# Configure logger
logger = logging.getLogger(__name__)
# Security scheme for OpenAPI documentation
security = HTTPBearer(
scheme_name="JWT Bearer Token",
description="JWT Bearer токен для авторизации. Получите токен через User Service /api/v1/auth/login"
)
# CORS middleware
app.add_middleware(
@@ -64,19 +93,239 @@ app.add_middleware(
)
class WebSocketManager:
"""Manage WebSocket connections for emergency notifications"""
def __init__(self):
self.active_connections: Dict[int, WebSocket] = {}
self.connection_info: Dict[int, dict] = {} # Дополнительная информация о подключениях
async def connect(self, websocket: WebSocket, user_id: int):
"""Connect a WebSocket for a specific user"""
await websocket.accept()
self.active_connections[user_id] = websocket
# Сохраняем информацию о подключении
self.connection_info[user_id] = {
"connected_at": datetime.now(),
"client_host": websocket.client.host if websocket.client else "unknown",
"client_port": websocket.client.port if websocket.client else 0,
"last_ping": datetime.now(),
"message_count": 0,
"status": "connected"
}
print(f"WebSocket connected for user {user_id} from {websocket.client}")
# Отправляем приветственное сообщение
await self.send_personal_message(json.dumps({
"type": "connection_established",
"message": "WebSocket connection established successfully",
"user_id": user_id,
"timestamp": datetime.now().isoformat()
}), user_id)
def disconnect(self, user_id: int):
"""Disconnect a WebSocket for a specific user"""
if user_id in self.active_connections:
del self.active_connections[user_id]
if user_id in self.connection_info:
self.connection_info[user_id]["status"] = "disconnected"
self.connection_info[user_id]["disconnected_at"] = datetime.now()
print(f"WebSocket disconnected for user {user_id}")
async def send_personal_message(self, message: str, user_id: int):
"""Send a message to a specific user"""
if user_id in self.active_connections:
websocket = self.active_connections[user_id]
try:
await websocket.send_text(message)
# Обновляем статистику
if user_id in self.connection_info:
self.connection_info[user_id]["message_count"] += 1
self.connection_info[user_id]["last_ping"] = datetime.now()
except Exception as e:
print(f"Error sending message to user {user_id}: {e}")
self.disconnect(user_id)
async def broadcast_alert(self, alert_data: dict, user_ids: Optional[List[int]] = None):
"""Broadcast alert to specific users or all connected users"""
message = json.dumps({
"type": "emergency_alert",
"data": alert_data
})
target_users = user_ids if user_ids else list(self.active_connections.keys())
for user_id in target_users:
await self.send_personal_message(message, user_id)
async def send_alert_update(self, alert_id: int, alert_data: dict, user_ids: Optional[List[int]] = None):
"""Send alert update to specific users"""
message = json.dumps({
"type": "alert_update",
"alert_id": alert_id,
"data": alert_data
})
target_users = user_ids if user_ids else list(self.active_connections.keys())
for user_id in target_users:
await self.send_personal_message(message, user_id)
def get_connected_users_count(self) -> int:
"""Получить количество подключенных пользователей"""
return len(self.active_connections)
def get_connected_users_list(self) -> List[int]:
"""Получить список ID подключенных пользователей"""
return list(self.active_connections.keys())
def get_connection_info(self, user_id: Optional[int] = None) -> dict:
"""Получить информацию о подключениях"""
if user_id:
return self.connection_info.get(user_id, {})
# Возвращаем общую статистику
active_count = len(self.active_connections)
total_messages = sum(info.get("message_count", 0) for info in self.connection_info.values())
connection_details = {}
for user_id, info in self.connection_info.items():
if info.get("status") == "connected":
connected_at = info.get("connected_at")
last_ping = info.get("last_ping")
connection_details[user_id] = {
"connected_at": connected_at.isoformat() if connected_at else None,
"client_host": info.get("client_host"),
"client_port": info.get("client_port"),
"last_ping": last_ping.isoformat() if last_ping else None,
"message_count": info.get("message_count", 0),
"status": info.get("status"),
"duration_seconds": int((datetime.now() - connected_at).total_seconds())
if connected_at and info.get("status") == "connected" else None
}
return {
"active_connections": active_count,
"total_messages_sent": total_messages,
"connected_users": list(self.active_connections.keys()),
"connection_details": connection_details
}
async def ping_all_connections(self):
"""Проверить все WebSocket подключения"""
disconnected_users = []
for user_id, websocket in list(self.active_connections.items()):
try:
ping_message = json.dumps({
"type": "ping",
"timestamp": datetime.now().isoformat()
})
await websocket.send_text(ping_message)
# Обновляем время последнего пинга
if user_id in self.connection_info:
self.connection_info[user_id]["last_ping"] = datetime.now()
except Exception as e:
print(f"Connection lost for user {user_id}: {e}")
disconnected_users.append(user_id)
# Удаляем неактивные подключения
for user_id in disconnected_users:
self.disconnect(user_id)
return {
"active_connections": len(self.active_connections),
"disconnected_users": disconnected_users,
"ping_time": datetime.now().isoformat()
}
# Global WebSocket manager instance
ws_manager = WebSocketManager()
async def get_current_user(
user_data: dict = Depends(get_current_user_from_token),
credentials: HTTPAuthorizationCredentials = Depends(security),
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:
"""
Get current user from JWT Bearer token for OpenAPI documentation.
Требует Bearer токен в заголовке Authorization:
Authorization: Bearer <your_jwt_token>
Returns simplified User object to avoid SQLAlchemy issues.
"""
try:
# Получаем данные пользователя из токена напрямую
from shared.auth import verify_token
user_data = verify_token(credentials.credentials)
if user_data is None:
raise HTTPException(
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
# Возвращаем упрощенный объект пользователя
return type('User', (), {
'id': user_data["user_id"],
'email': user_data.get("email", "unknown@example.com"),
'username': user_data.get("username", f"user_{user_data['user_id']}")
})()
except HTTPException:
raise
except Exception as e:
logger.error(f"Authentication failed: {str(e)}")
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="User not found"
status_code=status.HTTP_401_UNAUTHORIZED,
detail="Could not validate credentials",
headers={"WWW-Authenticate": "Bearer"},
)
return user
async def get_current_user_websocket(token: str):
"""Get current user from WebSocket token - PRODUCTION READY"""
try:
from shared.auth import verify_token
import logging
# Логируем попытку аутентификации (без токена в логах!)
print(f"🔐 WebSocket auth: Attempting authentication for token length={len(token)}")
# ВАЖНО: Никаких заглушек! Только настоящие JWT токены
if token.startswith("temp_token") or token.startswith("test_token"):
print(f"❌ WebSocket auth: REJECTED - Temporary tokens not allowed in production!")
print(f"❌ Token prefix: {token[:20]}...")
return None
# Проверяем JWT токен
user_data = verify_token(token)
if not user_data:
print(f"❌ WebSocket auth: Invalid or expired JWT token")
return None
print(f"✅ WebSocket auth: JWT token valid for user_id={user_data['user_id']}, email={user_data.get('email', 'N/A')}")
# Создаем объект пользователя из токена
class AuthenticatedUser:
def __init__(self, user_id, email):
self.id = user_id
self.email = email
return AuthenticatedUser(user_data['user_id'], user_data.get('email', f'user_{user_data["user_id"]}'))
except Exception as e:
print(f"❌ WebSocket auth error: {e}")
return None
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
@@ -100,6 +349,86 @@ async def health_check():
return {"status": "healthy", "service": "emergency_service"}
@app.websocket("/api/v1/emergency/ws/{user_id}")
async def websocket_endpoint(websocket: WebSocket, user_id: str):
"""WebSocket endpoint for emergency notifications"""
print(f"🔌 WebSocket connection attempt from {websocket.client}")
print(f"📝 user_id: {user_id}")
print(f"🔗 Query params: {dict(websocket.query_params)}")
# Get token from query parameter
token = websocket.query_params.get("token")
print(f"🎫 Token received: {token}")
if not token:
print("❌ No token provided, closing connection")
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
# Authenticate user
authenticated_user = await get_current_user_websocket(token)
if not authenticated_user:
print("❌ Authentication failed, closing connection")
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
# Get user ID as integer - authenticated_user is an instance with id attribute
auth_user_id = authenticated_user.id
print(f"✅ User authenticated: {authenticated_user.email} (ID: {auth_user_id})")
# Verify user_id matches authenticated user
try:
if int(user_id) != auth_user_id:
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
except ValueError:
if user_id != "current_user_id":
await websocket.close(code=status.WS_1008_POLICY_VIOLATION)
return
# Handle special case where client uses 'current_user_id' as placeholder
user_id = str(auth_user_id)
# Connect WebSocket
await ws_manager.connect(websocket, auth_user_id)
try:
# Send initial connection message
await ws_manager.send_personal_message(
json.dumps({
"type": "connection_established",
"message": "Connected to emergency notifications",
"user_id": auth_user_id
}),
auth_user_id
)
# Keep connection alive and listen for messages
while True:
try:
# Wait for messages (ping/pong, etc.)
data = await websocket.receive_text()
message = json.loads(data)
# Handle different message types
if message.get("type") == "ping":
await ws_manager.send_personal_message(
json.dumps({"type": "pong"}),
auth_user_id
)
except WebSocketDisconnect:
break
except Exception as e:
print(f"WebSocket error: {e}")
break
except WebSocketDisconnect:
pass
finally:
ws_manager.disconnect(auth_user_id)
async def get_nearby_users(
latitude: float, longitude: float, radius_km: float = 1.0
) -> List[dict]:
@@ -122,20 +451,76 @@ async def get_nearby_users(
return []
async def send_websocket_notifications_to_nearby_users(alert, nearby_users: List[dict]) -> int:
"""Send real-time WebSocket notifications to nearby users who are online"""
online_count = 0
# Create notification message
notification = {
"type": "emergency_alert",
"alert_id": alert.id,
"alert_type": alert.alert_type,
"latitude": alert.latitude,
"longitude": alert.longitude,
"address": alert.address,
"message": alert.message or "Экстренная ситуация рядом с вами!",
"created_at": alert.created_at.isoformat(),
"distance_km": None # Will be calculated per user
}
print(f"🔔 Sending WebSocket notifications to {len(nearby_users)} nearby users")
for user in nearby_users:
user_id = user.get("user_id")
distance = user.get("distance_km", 0)
# Update distance in notification
notification["distance_km"] = round(distance, 2)
# Check if user has active WebSocket connection
if user_id in ws_manager.active_connections:
try:
# Send notification via WebSocket
await ws_manager.send_personal_message(
json.dumps(notification, ensure_ascii=False, default=str),
user_id
)
online_count += 1
print(f"📡 Sent WebSocket notification to user {user_id} ({distance:.1f}km away)")
except Exception as e:
print(f"❌ Failed to send WebSocket to user {user_id}: {e}")
else:
print(f"💤 User {user_id} is offline - will receive push notification only")
print(f"✅ WebSocket notifications sent to {online_count}/{len(nearby_users)} online users")
return online_count
async def send_emergency_notifications(alert_id: int, nearby_users: List[dict]):
"""Send push notifications to nearby users"""
if not nearby_users:
return
print(f"📱 Sending push notifications to {len(nearby_users)} users via Notification Service")
async with httpx.AsyncClient() as client:
try:
await client.post(
response = 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],
"message": "🚨 Экстренная ситуация рядом с вами! Проверьте приложение.",
"title": "Экстренное уведомление"
},
timeout=10.0,
)
if response.status_code == 200:
print(f"✅ Push notifications sent successfully")
else:
print(f"⚠️ Push notification service responded with {response.status_code}")
except Exception as e:
print(f"Failed to send notifications: {e}")
print(f"Failed to send push notifications: {e}")
@app.post("/api/v1/alert", response_model=EmergencyAlertResponse)
@@ -172,16 +557,27 @@ async def create_emergency_alert(
async def process_emergency_alert_in_background(alert_id: int, latitude: float, longitude: float):
"""Process emergency alert - notify nearby users"""
"""Process emergency alert - notify nearby users via WebSocket and Push notifications"""
try:
# Get nearby users
nearby_users = await get_nearby_users(latitude, longitude)
print(f"🚨 Processing emergency alert {alert_id} at coordinates ({latitude}, {longitude})")
# Get nearby users within 5km radius
nearby_users = await get_nearby_users(latitude, longitude, radius_km=5.0)
print(f"📍 Found {len(nearby_users)} nearby users within 5km radius")
if nearby_users:
# Create new database session for background task
from shared.database import AsyncSessionLocal
async with AsyncSessionLocal() as db:
try:
# Get full alert details for notifications
result = await db.execute(select(EmergencyAlert).filter(EmergencyAlert.id == alert_id))
alert = result.scalars().first()
if not alert:
print(f"❌ Alert {alert_id} not found in database")
return
# Update alert with notification count
await db.execute(
update(EmergencyAlert)
@@ -189,16 +585,25 @@ async def process_emergency_alert_in_background(alert_id: int, latitude: float,
.values(notified_users_count=len(nearby_users))
)
await db.commit()
print(f"✅ Updated alert {alert_id} with {len(nearby_users)} notified users")
# Send notifications
# Send real-time WebSocket notifications to online users
online_notifications_sent = await send_websocket_notifications_to_nearby_users(alert, nearby_users)
# Send push notifications via notification service
await send_emergency_notifications(alert_id, nearby_users)
print(f"📱 Sent notifications: {online_notifications_sent} WebSocket + {len(nearby_users)} Push")
except Exception as e:
print(f"Error processing emergency alert: {e}")
print(f"Error processing emergency alert: {e}")
await db.rollback()
else:
print(f" No nearby users found for alert {alert_id}")
except Exception as e:
print(f"Error in process_emergency_alert_in_background: {e}")
print(f"Error in process_emergency_alert_in_background: {e}")
@app.post("/api/v1/alert/{alert_id}/respond", response_model=EmergencyResponseResponse)
@@ -568,6 +973,285 @@ async def get_alert_responses(
return [EmergencyResponseResponse.model_validate(response) for response in responses]
@app.get("/api/v1/websocket/connections")
async def get_websocket_connections(
current_user: User = Depends(get_current_user)
):
"""Получить информацию о WebSocket подключениях"""
return ws_manager.get_connection_info()
@app.get("/api/v1/websocket/connections/{user_id}")
async def get_user_websocket_info(
user_id: int,
current_user: User = Depends(get_current_user)
):
"""Получить информацию о подключении конкретного пользователя"""
connection_info = ws_manager.get_connection_info(user_id)
if not connection_info:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="User connection not found"
)
return connection_info
@app.post("/api/v1/websocket/ping")
async def ping_websocket_connections(
current_user: User = Depends(get_current_user)
):
"""Проверить все WebSocket подключения (пинг)"""
result = await ws_manager.ping_all_connections()
return result
@app.get("/api/v1/websocket/stats")
async def get_websocket_stats(
current_user: User = Depends(get_current_user)
):
"""Получить общую статистику WebSocket подключений"""
info = ws_manager.get_connection_info()
return {
"total_connections": info["active_connections"],
"connected_users": info["connected_users"],
"total_messages_sent": info["total_messages_sent"],
"connection_count": len(info["connected_users"]),
"timestamp": datetime.now().isoformat()
}
@app.post("/api/v1/websocket/broadcast")
async def broadcast_test_message(
message: str,
current_user: User = Depends(get_current_user)
):
"""Отправить тестовое сообщение всем подключенным пользователям"""
test_data = {
"type": "test_broadcast",
"message": message,
"from_user": current_user.id,
"timestamp": datetime.now().isoformat()
}
await ws_manager.broadcast_alert(test_data)
return {
"message": "Test broadcast sent",
"recipients": ws_manager.get_connected_users_list(),
"data": test_data
}
# MOBILE APP COMPATIBILITY ENDPOINTS
# Мобильное приложение ожидает endpoints с /api/v1/emergency/events
@app.post("/api/v1/emergency/events", response_model=EmergencyAlertResponse)
async def create_emergency_event(
alert_data: EmergencyAlertCreate,
background_tasks: BackgroundTasks,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Create emergency event (alias for create_alert for mobile compatibility)"""
# Используем существующую логику создания alert
return await create_emergency_alert(alert_data, background_tasks, current_user, db)
@app.get("/api/v1/emergency/events/nearby", response_model=List[NearbyAlertResponse])
async def get_nearby_emergency_events(
latitude: float = Query(..., description="User latitude"),
longitude: float = Query(..., description="User longitude"),
radius: float = Query(5.0, description="Search radius in km"),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get nearby emergency events (alias for nearby alerts for mobile compatibility)"""
# Используем существующую логику поиска nearby alerts
return await get_nearby_alerts(latitude, longitude, radius, current_user, db)
@app.get("/api/v1/emergency/events", response_model=List[EmergencyAlertResponse])
async def get_emergency_events(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get all emergency events (alias for active alerts for mobile compatibility)"""
# Используем существующую логику получения активных alerts
return await get_active_alerts(current_user, db)
@app.get("/api/v1/emergency/events/my", response_model=List[EmergencyAlertResponse])
async def get_my_emergency_events(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get my emergency events (alias for my alerts for mobile compatibility)"""
# Используем существующую логику получения моих alerts
return await get_my_alerts(current_user, db)
@app.get("/api/v1/emergency/events/{event_id}", response_model=EmergencyEventDetails)
async def get_emergency_event(
event_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get full detailed information about emergency event by ID"""
try:
# Получаем alert с информацией о пользователе
alert_result = await db.execute(
select(EmergencyAlert, User)
.join(User, EmergencyAlert.user_id == User.id)
.filter(EmergencyAlert.id == event_id)
)
alert_data = alert_result.first()
if not alert_data:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Emergency event not found"
)
alert, user = alert_data
# Получаем все ответы на это событие с информацией о респондентах
responses_result = await db.execute(
select(EmergencyResponse, User)
.join(User, EmergencyResponse.responder_id == User.id)
.filter(EmergencyResponse.alert_id == event_id)
.order_by(EmergencyResponse.created_at.desc())
)
# Формируем список ответов
responses = []
for response_data in responses_result:
emergency_response, responder = response_data
# Формируем полное имя респондента
responder_name = responder.username
if responder.first_name and responder.last_name:
responder_name = f"{responder.first_name} {responder.last_name}"
elif responder.first_name:
responder_name = responder.first_name
elif responder.last_name:
responder_name = responder.last_name
response_dict = {
"id": emergency_response.id,
"alert_id": emergency_response.alert_id,
"responder_id": emergency_response.responder_id,
"response_type": emergency_response.response_type,
"message": emergency_response.message,
"eta_minutes": emergency_response.eta_minutes,
"created_at": emergency_response.created_at,
"responder_name": responder_name,
"responder_phone": responder.phone
}
responses.append(EmergencyResponseResponse(**response_dict))
# Создаем объект с информацией о пользователе
full_name = None
if user.first_name and user.last_name:
full_name = f"{user.first_name} {user.last_name}"
elif user.first_name:
full_name = user.first_name
elif user.last_name:
full_name = user.last_name
user_info = UserInfo(
id=user.id,
username=user.username,
full_name=full_name,
phone=user.phone
)
# Определяем статус события на основе is_resolved
from services.emergency_service.schemas import AlertStatus
event_status = AlertStatus.RESOLVED if alert.is_resolved else AlertStatus.ACTIVE
# Формируем полный ответ
event_details = EmergencyEventDetails(
id=alert.id,
uuid=alert.uuid,
user_id=alert.user_id,
latitude=alert.latitude,
longitude=alert.longitude,
address=alert.address,
alert_type=alert.alert_type,
message=alert.message,
status=event_status,
created_at=alert.created_at,
updated_at=alert.updated_at,
resolved_at=alert.resolved_at,
user=user_info,
responses=responses,
notifications_sent=len(responses), # Примерная статистика
websocket_notifications_sent=alert.notified_users_count or 0,
push_notifications_sent=alert.responded_users_count or 0,
contact_emergency_services=True, # Значение по умолчанию
notify_emergency_contacts=True # Значение по умолчанию
)
logger.info(f"Retrieved detailed event info for event_id={event_id}, responses_count={len(responses)}")
return event_details
except HTTPException:
raise
except Exception as e:
logger.error(f"Error retrieving emergency event {event_id}: {str(e)}")
raise HTTPException(
status_code=status.HTTP_500_INTERNAL_SERVER_ERROR,
detail="Failed to retrieve emergency event details"
)
@app.get("/api/v1/emergency/events/{event_id}/brief", response_model=EmergencyAlertResponse)
async def get_emergency_event_brief(
event_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get brief information about emergency event by ID (for mobile apps)"""
# Получаем конкретный alert
result = await db.execute(select(EmergencyAlert).filter(EmergencyAlert.id == event_id))
alert = result.scalars().first()
if not alert:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND,
detail="Emergency event not found"
)
logger.info(f"Retrieved brief event info for event_id={event_id}")
return EmergencyAlertResponse.model_validate(alert)
@app.put("/api/v1/emergency/events/{event_id}/resolve")
async def resolve_emergency_event(
event_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Resolve emergency event (alias for resolve alert)"""
# Используем существующую логику resolve alert
return await resolve_alert(event_id, current_user, db)
@app.post("/api/v1/emergency/events/{event_id}/respond", response_model=EmergencyResponseResponse)
async def respond_to_emergency_event(
event_id: int,
response: EmergencyResponseCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Respond to emergency event (alias for respond to alert)"""
# Используем существующую логику respond to alert
return await respond_to_alert(event_id, response, current_user, db)
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8002)

View File

@@ -96,6 +96,52 @@ class EmergencyResponseResponse(BaseModel):
from_attributes = True
class UserInfo(BaseModel):
"""Базовая информация о пользователе для событий"""
id: int
username: str
full_name: Optional[str] = None
phone: Optional[str] = None
class Config:
from_attributes = True
class EmergencyEventDetails(BaseModel):
"""Полная детальная информация о событии экстренной помощи"""
# Основная информация о событии
id: int
uuid: UUID
user_id: int
latitude: float
longitude: float
address: Optional[str] = None
alert_type: AlertType
message: Optional[str] = None
status: AlertStatus
created_at: datetime
updated_at: Optional[datetime] = None
resolved_at: Optional[datetime] = None
# Информация о пользователе, который создал событие
user: UserInfo
# Все ответы на это событие
responses: List[EmergencyResponseResponse] = []
# Статистика уведомлений
notifications_sent: int = 0
websocket_notifications_sent: int = 0
push_notifications_sent: int = 0
# Дополнительная информация
contact_emergency_services: bool = True
notify_emergency_contacts: bool = True
class Config:
from_attributes = True
# Report schemas
class EmergencyReportCreate(BaseModel):
latitude: float = Field(..., ge=-90, le=90)