@@ -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 S ervice
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_s ervice
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 notification s
# Send real-time WebSocket notifications to online user s
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 )