This commit is contained in:
617
docs/DATA_SCHEMAS.md
Normal file
617
docs/DATA_SCHEMAS.md
Normal file
@@ -0,0 +1,617 @@
|
||||
# Полные схемы данных для мобильного приложения Women's Safety App
|
||||
|
||||
## 📋 Содержание
|
||||
1. [Схемы авторизации](#схемы-авторизации)
|
||||
2. [Схемы пользователей](#схемы-пользователей)
|
||||
3. [Схемы экстренных ситуаций](#схемы-экстренных-ситуаций)
|
||||
4. [Схемы местоположения](#схемы-местоположения)
|
||||
5. [Схемы уведомлений](#схемы-уведомлений)
|
||||
6. [Схемы календаря](#схемы-календаря)
|
||||
7. [TypeScript интерфейсы](#typescript-интерфейсы)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Схемы авторизации
|
||||
|
||||
### UserRegister
|
||||
```json
|
||||
{
|
||||
"username": "string", // Имя пользователя (уникальное)
|
||||
"email": "string", // Email (уникальный)
|
||||
"password": "string", // Пароль (мин 8 символов)
|
||||
"full_name": "string?", // Полное имя
|
||||
"phone": "string?", // Номер телефона
|
||||
"date_of_birth": "date?", // Дата рождения (YYYY-MM-DD)
|
||||
"bio": "string?" // Биография (макс 500 символов)
|
||||
}
|
||||
```
|
||||
|
||||
### UserLogin
|
||||
```json
|
||||
{
|
||||
"username": "string?", // Имя пользователя ИЛИ
|
||||
"email": "string?", // Email (один из двух обязателен)
|
||||
"password": "string" // Пароль
|
||||
}
|
||||
```
|
||||
|
||||
### TokenResponse
|
||||
```json
|
||||
{
|
||||
"access_token": "string", // JWT токен
|
||||
"token_type": "bearer" // Тип токена
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 👤 Схемы пользователей
|
||||
|
||||
### UserProfile (полная информация)
|
||||
```json
|
||||
{
|
||||
"id": 123, // int, ID пользователя
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID пользователя
|
||||
"username": "testuser", // string, имя пользователя
|
||||
"email": "test@example.com", // string, email
|
||||
"phone": "+1234567890", // string?, телефон
|
||||
"first_name": "John", // string?, имя
|
||||
"last_name": "Doe", // string?, фамилия
|
||||
"date_of_birth": "1990-01-01", // date?, дата рождения
|
||||
"bio": "Краткая биография", // string?, биография
|
||||
"avatar_url": "https://...", // string?, URL аватара
|
||||
"location_sharing_enabled": true, // bool, разрешение на геолокацию
|
||||
"emergency_notifications_enabled": true, // bool, экстренные уведомления
|
||||
"push_notifications_enabled": true, // bool, push уведомления
|
||||
"email_verified": false, // bool, email подтвержден
|
||||
"phone_verified": true, // bool, телефон подтвержден
|
||||
"is_active": true, // bool, активен ли аккаунт
|
||||
"created_at": "2024-01-01T10:00:00Z", // datetime, дата регистрации
|
||||
"updated_at": "2024-01-15T10:00:00Z" // datetime, последнее обновление
|
||||
}
|
||||
```
|
||||
|
||||
### UserDashboard
|
||||
```json
|
||||
{
|
||||
"user_info": { // Краткая информация о пользователе
|
||||
"id": 123,
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"first_name": "John",
|
||||
"last_name": "Doe"
|
||||
},
|
||||
"recent_alerts": [ // Последние оповещения (макс 5)
|
||||
{
|
||||
"id": 456,
|
||||
"alert_type": "general",
|
||||
"message": "Нужна помощь",
|
||||
"created_at": "2024-01-15T09:30:00Z",
|
||||
"is_resolved": false,
|
||||
"responded_users_count": 2
|
||||
}
|
||||
],
|
||||
"emergency_contacts": [ // Экстренные контакты
|
||||
{
|
||||
"id": 789,
|
||||
"name": "Мама",
|
||||
"phone_number": "+1234567890",
|
||||
"relationship": "mother"
|
||||
}
|
||||
],
|
||||
"safety_stats": { // Статистика безопасности
|
||||
"total_alerts_created": 5,
|
||||
"total_responses_given": 12,
|
||||
"safety_checks_count": 45,
|
||||
"last_safety_check": "2024-01-15T08:00:00Z"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### EmergencyContact
|
||||
```json
|
||||
{
|
||||
"id": 789, // int, ID контакта
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID контакта
|
||||
"user_id": 123, // int, ID владельца
|
||||
"name": "Мама", // string, имя контакта
|
||||
"phone_number": "+1234567890", // string, телефон
|
||||
"relationship": "mother", // string?, отношение (mother, father, spouse, friend, etc.)
|
||||
"notes": "Основной контакт", // string?, заметки
|
||||
"is_active": true, // bool, активен ли контакт
|
||||
"created_at": "2024-01-01T10:00:00Z", // datetime, дата создания
|
||||
"updated_at": "2024-01-15T10:00:00Z" // datetime, последнее обновление
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Схемы экстренных ситуаций
|
||||
|
||||
### EmergencyAlert (полная схема)
|
||||
```json
|
||||
{
|
||||
"id": 123, // int, ID оповещения
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID оповещения
|
||||
"user_id": 26, // int, ID создателя
|
||||
"latitude": 55.7558, // float, широта (-90 до 90)
|
||||
"longitude": 37.6176, // float, долгота (-180 до 180)
|
||||
"address": "Красная площадь, Москва", // string?, адрес
|
||||
"alert_type": "general", // AlertType, тип оповещения
|
||||
"status": "active", // AlertStatus, текущий статус
|
||||
"message": "Нужна помощь", // string?, описание ситуации
|
||||
"is_resolved": false, // bool, решено ли
|
||||
"resolved_at": null, // datetime?, время решения
|
||||
"resolved_by": null, // int?, кем решено
|
||||
"resolved_notes": null, // string?, заметки о решении
|
||||
"contact_emergency_services": true, // bool, связаться со службами
|
||||
"notify_emergency_contacts": true, // bool, уведомить контакты
|
||||
"notified_users_count": 5, // int, количество уведомленных
|
||||
"responded_users_count": 2, // int, количество откликнувшихся
|
||||
"created_at": "2024-01-15T10:30:00Z", // datetime, время создания
|
||||
"updated_at": "2024-01-15T10:35:00Z" // datetime, последнее обновление
|
||||
}
|
||||
```
|
||||
|
||||
### EmergencyResponse (отклик на оповещение)
|
||||
```json
|
||||
{
|
||||
"id": 456, // int, ID отклика
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID отклика
|
||||
"alert_id": 123, // int, ID оповещения
|
||||
"user_id": 27, // int, ID откликнувшегося
|
||||
"response_type": "help_on_way", // ResponseType, тип отклика
|
||||
"message": "Еду к вам!", // string?, сообщение
|
||||
"eta_minutes": 15, // int?, время прибытия в минутах
|
||||
"location_sharing": true, // bool, делиться местоположением
|
||||
"created_at": "2024-01-15T10:32:00Z" // datetime, время создания
|
||||
}
|
||||
```
|
||||
|
||||
### EmergencyReport (отчет о происшествии)
|
||||
```json
|
||||
{
|
||||
"id": 789, // int, ID отчета
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID отчета
|
||||
"user_id": 26, // int?, ID автора (null для анонимных)
|
||||
"latitude": 55.7558, // float, широта
|
||||
"longitude": 37.6176, // float, долгота
|
||||
"address": "Тверская улица", // string?, адрес
|
||||
"report_type": "harassment", // string, тип происшествия
|
||||
"description": "Подробное описание...", // string, описание (10-1000 символов)
|
||||
"is_anonymous": false, // bool, анонимный отчет
|
||||
"severity": 4, // int, серьезность (1-5)
|
||||
"status": "pending", // string, статус (pending/investigating/resolved)
|
||||
"created_at": "2024-01-15T10:45:00Z" // datetime, время создания
|
||||
}
|
||||
```
|
||||
|
||||
### SafetyCheck (отметка безопасности)
|
||||
```json
|
||||
{
|
||||
"id": 101, // int, ID отметки
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID отметки
|
||||
"user_id": 26, // int, ID пользователя
|
||||
"message": "Добрался домой безопасно", // string?, сообщение
|
||||
"location_latitude": 55.7600, // float?, широта
|
||||
"location_longitude": 37.6100, // float?, долгота
|
||||
"created_at": "2024-01-15T22:00:00Z" // datetime, время создания
|
||||
}
|
||||
```
|
||||
|
||||
### EmergencyStatistics
|
||||
```json
|
||||
{
|
||||
"total_alerts": 150, // int, общее количество оповещений
|
||||
"active_alerts": 12, // int, активные оповещения
|
||||
"resolved_alerts": 138, // int, решенные оповещения
|
||||
"total_responders": 89, // int, всего откликнувшихся
|
||||
"avg_response_time_minutes": 8.5 // float, среднее время отклика
|
||||
}
|
||||
```
|
||||
|
||||
### NearbyAlert (ближайшие оповещения)
|
||||
```json
|
||||
{
|
||||
"id": 123, // int, ID оповещения
|
||||
"alert_type": "medical", // string, тип оповещения
|
||||
"latitude": 55.7558, // float, широта
|
||||
"longitude": 37.6176, // float, долгота
|
||||
"address": "Больница №1", // string?, адрес
|
||||
"distance_km": 2.5, // float, расстояние в километрах
|
||||
"created_at": "2024-01-15T09:15:00Z", // datetime, время создания
|
||||
"responded_users_count": 3 // int, количество откликов
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📍 Схемы местоположения
|
||||
|
||||
### LocationUpdate
|
||||
```json
|
||||
{
|
||||
"latitude": 55.7558, // float, широта (-90 до 90)
|
||||
"longitude": 37.6176, // float, долгота (-180 до 180)
|
||||
"accuracy": 10.0, // float?, точность в метрах
|
||||
"altitude": 150.0, // float?, высота в метрах
|
||||
"speed": 0.0, // float?, скорость м/с
|
||||
"heading": 90.0, // float?, направление (0-360 градусов)
|
||||
"timestamp": "2024-01-15T10:30:00Z" // datetime?, время получения координат
|
||||
}
|
||||
```
|
||||
|
||||
### NearbyUser
|
||||
```json
|
||||
{
|
||||
"user_id": 27, // int, ID пользователя
|
||||
"distance_meters": 500.0, // float, расстояние в метрах
|
||||
"latitude": 55.7568, // float, широта (может быть приблизительной)
|
||||
"longitude": 37.6186, // float, долгота (может быть приблизительной)
|
||||
"last_seen": "2024-01-15T10:25:00Z", // datetime, последнее обновление местоположения
|
||||
"is_available": true // bool, готов ли помочь
|
||||
}
|
||||
```
|
||||
|
||||
### GeocodeResult
|
||||
```json
|
||||
{
|
||||
"address": "Красная площадь, 1, Москва, Россия", // string, полный адрес
|
||||
"street": "Красная площадь", // string?, улица
|
||||
"house_number": "1", // string?, номер дома
|
||||
"city": "Москва", // string?, город
|
||||
"state": "Москва", // string?, регион/область
|
||||
"country": "Россия", // string?, страна
|
||||
"postal_code": "109012", // string?, почтовый индекс
|
||||
"latitude": 55.7558, // float, широта
|
||||
"longitude": 37.6176 // float, долгота
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔔 Схемы уведомлений
|
||||
|
||||
### NotificationCreate
|
||||
```json
|
||||
{
|
||||
"user_id": 123, // int, ID получателя
|
||||
"title": "Экстренное оповещение", // string, заголовок
|
||||
"message": "Новое оповещение рядом с вами", // string, текст сообщения
|
||||
"type": "emergency_alert", // string, тип уведомления
|
||||
"data": { // object?, дополнительные данные
|
||||
"alert_id": 456,
|
||||
"latitude": 55.7558,
|
||||
"longitude": 37.6176
|
||||
},
|
||||
"priority": "high", // string?, приоритет (low/normal/high/urgent)
|
||||
"schedule_at": null // datetime?, время отправки (null = сейчас)
|
||||
}
|
||||
```
|
||||
|
||||
### PushTokenRegistration
|
||||
```json
|
||||
{
|
||||
"token": "fcm_or_apns_token_here", // string, токен устройства
|
||||
"platform": "ios", // string, платформа (ios/android)
|
||||
"app_version": "1.0.0", // string?, версия приложения
|
||||
"device_info": { // object?, информация об устройстве
|
||||
"model": "iPhone 14",
|
||||
"os_version": "17.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### NotificationHistory
|
||||
```json
|
||||
{
|
||||
"id": 789, // int, ID уведомления
|
||||
"user_id": 123, // int, ID получателя
|
||||
"title": "Экстренное оповещение", // string, заголовок
|
||||
"message": "Текст уведомления", // string, сообщение
|
||||
"type": "emergency_alert", // string, тип
|
||||
"status": "delivered", // string, статус (sent/delivered/read/failed)
|
||||
"sent_at": "2024-01-15T10:30:00Z", // datetime, время отправки
|
||||
"delivered_at": "2024-01-15T10:30:05Z", // datetime?, время доставки
|
||||
"read_at": null // datetime?, время прочтения
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📅 Схемы календаря
|
||||
|
||||
### CalendarEntry
|
||||
```json
|
||||
{
|
||||
"id": 456, // int, ID записи
|
||||
"user_id": 123, // int, ID пользователя
|
||||
"date": "2024-01-15", // date, дата записи (YYYY-MM-DD)
|
||||
"entry_type": "period_start", // string, тип записи
|
||||
"notes": "Болезненные ощущения", // string?, заметки
|
||||
"mood_score": 3, // int?, настроение (1-5)
|
||||
"energy_level": 4, // int?, уровень энергии (1-5)
|
||||
"symptoms": [ // array?, симптомы
|
||||
"headache",
|
||||
"fatigue",
|
||||
"cramps"
|
||||
],
|
||||
"flow_intensity": "medium", // string?, интенсивность (light/medium/heavy)
|
||||
"temperature": 36.6, // float?, температура тела
|
||||
"weight": 65.5, // float?, вес
|
||||
"created_at": "2024-01-15T08:00:00Z", // datetime, время создания
|
||||
"updated_at": "2024-01-15T08:05:00Z" // datetime, последнее обновление
|
||||
}
|
||||
```
|
||||
|
||||
### CalendarAnalytics
|
||||
```json
|
||||
{
|
||||
"cycle_length": 28, // int?, средняя длина цикла
|
||||
"period_length": 5, // int?, средняя длина месячных
|
||||
"next_period_prediction": "2024-02-10", // date?, прогноз следующих месячных
|
||||
"fertility_window": { // object?, окно фертильности
|
||||
"start": "2024-01-20",
|
||||
"end": "2024-01-25"
|
||||
},
|
||||
"mood_trends": { // object?, тренды настроения
|
||||
"average_score": 3.5,
|
||||
"lowest_day": 2,
|
||||
"highest_day": 12
|
||||
},
|
||||
"symptoms_frequency": { // object?, частота симптомов
|
||||
"headache": 0.3,
|
||||
"cramps": 0.8,
|
||||
"fatigue": 0.6
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 TypeScript интерфейсы
|
||||
|
||||
```typescript
|
||||
// Перечисления
|
||||
export enum AlertType {
|
||||
GENERAL = 'general',
|
||||
MEDICAL = 'medical',
|
||||
VIOLENCE = 'violence',
|
||||
HARASSMENT = 'harassment',
|
||||
UNSAFE_AREA = 'unsafe_area',
|
||||
ACCIDENT = 'accident',
|
||||
FIRE = 'fire',
|
||||
NATURAL_DISASTER = 'natural_disaster'
|
||||
}
|
||||
|
||||
export enum AlertStatus {
|
||||
ACTIVE = 'active',
|
||||
RESOLVED = 'resolved',
|
||||
CANCELLED = 'cancelled',
|
||||
INVESTIGATING = 'investigating'
|
||||
}
|
||||
|
||||
export enum ResponseType {
|
||||
HELP_ON_WAY = 'help_on_way',
|
||||
CONTACTED_AUTHORITIES = 'contacted_authorities',
|
||||
SAFE_NOW = 'safe_now',
|
||||
FALSE_ALARM = 'false_alarm',
|
||||
INVESTIGATING = 'investigating',
|
||||
RESOLVED = 'resolved'
|
||||
}
|
||||
|
||||
// Интерфейсы авторизации
|
||||
export interface UserRegister {
|
||||
username: string;
|
||||
email: string;
|
||||
password: string;
|
||||
full_name?: string;
|
||||
phone?: string;
|
||||
date_of_birth?: string;
|
||||
bio?: string;
|
||||
}
|
||||
|
||||
export interface UserLogin {
|
||||
username?: string;
|
||||
email?: string;
|
||||
password: string;
|
||||
}
|
||||
|
||||
export interface TokenResponse {
|
||||
access_token: string;
|
||||
token_type: string;
|
||||
}
|
||||
|
||||
// Интерфейсы пользователя
|
||||
export interface UserProfile {
|
||||
id: number;
|
||||
uuid: string;
|
||||
username: string;
|
||||
email: string;
|
||||
phone?: string;
|
||||
first_name?: string;
|
||||
last_name?: string;
|
||||
date_of_birth?: string;
|
||||
bio?: string;
|
||||
avatar_url?: string;
|
||||
location_sharing_enabled: boolean;
|
||||
emergency_notifications_enabled: boolean;
|
||||
push_notifications_enabled: boolean;
|
||||
email_verified: boolean;
|
||||
phone_verified: boolean;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface EmergencyContact {
|
||||
id: number;
|
||||
uuid: string;
|
||||
user_id: number;
|
||||
name: string;
|
||||
phone_number: string;
|
||||
relationship?: string;
|
||||
notes?: string;
|
||||
is_active: boolean;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
// Интерфейсы экстренных ситуаций
|
||||
export interface EmergencyAlertCreate {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
alert_type: AlertType;
|
||||
message?: string;
|
||||
address?: string;
|
||||
contact_emergency_services?: boolean;
|
||||
notify_emergency_contacts?: boolean;
|
||||
}
|
||||
|
||||
export interface EmergencyAlert {
|
||||
id: number;
|
||||
uuid: string;
|
||||
user_id: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
address?: string;
|
||||
alert_type: AlertType;
|
||||
status: AlertStatus;
|
||||
message?: string;
|
||||
is_resolved: boolean;
|
||||
resolved_at?: string;
|
||||
resolved_by?: number;
|
||||
resolved_notes?: string;
|
||||
contact_emergency_services: boolean;
|
||||
notify_emergency_contacts: boolean;
|
||||
notified_users_count: number;
|
||||
responded_users_count: number;
|
||||
created_at: string;
|
||||
updated_at?: string;
|
||||
}
|
||||
|
||||
export interface EmergencyResponseCreate {
|
||||
response_type: ResponseType;
|
||||
message?: string;
|
||||
eta_minutes?: number;
|
||||
location_sharing?: boolean;
|
||||
}
|
||||
|
||||
export interface EmergencyResponse {
|
||||
id: number;
|
||||
uuid: string;
|
||||
alert_id: number;
|
||||
user_id: number;
|
||||
response_type: ResponseType;
|
||||
message?: string;
|
||||
eta_minutes?: number;
|
||||
location_sharing: boolean;
|
||||
created_at: string;
|
||||
}
|
||||
|
||||
export interface EmergencyStatistics {
|
||||
total_alerts: number;
|
||||
active_alerts: number;
|
||||
resolved_alerts: number;
|
||||
total_responders: number;
|
||||
avg_response_time_minutes: number;
|
||||
}
|
||||
|
||||
export interface NearbyAlert {
|
||||
id: number;
|
||||
alert_type: string;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
address?: string;
|
||||
distance_km: number;
|
||||
created_at: string;
|
||||
responded_users_count: number;
|
||||
}
|
||||
|
||||
// Интерфейсы местоположения
|
||||
export interface LocationUpdate {
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
accuracy?: number;
|
||||
altitude?: number;
|
||||
speed?: number;
|
||||
heading?: number;
|
||||
timestamp?: string;
|
||||
}
|
||||
|
||||
export interface NearbyUser {
|
||||
user_id: number;
|
||||
distance_meters: number;
|
||||
latitude: number;
|
||||
longitude: number;
|
||||
last_seen: string;
|
||||
is_available: boolean;
|
||||
}
|
||||
|
||||
// Интерфейсы уведомлений
|
||||
export interface PushTokenRegistration {
|
||||
token: string;
|
||||
platform: 'ios' | 'android';
|
||||
app_version?: string;
|
||||
device_info?: {
|
||||
model?: string;
|
||||
os_version?: string;
|
||||
};
|
||||
}
|
||||
|
||||
// API клиент
|
||||
export interface APIClient {
|
||||
setToken(token: string): void;
|
||||
login(username: string, password: string): Promise<TokenResponse>;
|
||||
register(userData: UserRegister): Promise<UserProfile>;
|
||||
getProfile(): Promise<UserProfile>;
|
||||
createAlert(alertData: EmergencyAlertCreate): Promise<EmergencyAlert>;
|
||||
getNearbyAlerts(lat: number, lng: number, radius?: number): Promise<NearbyAlert[]>;
|
||||
respondToAlert(alertId: number, response: EmergencyResponseCreate): Promise<EmergencyResponse>;
|
||||
updateLocation(location: LocationUpdate): Promise<void>;
|
||||
createSafetyCheck(data: { message?: string; location_latitude?: number; location_longitude?: number }): Promise<any>;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📋 Валидация данных
|
||||
|
||||
### Ограничения полей
|
||||
- **Координаты**: latitude (-90 до 90), longitude (-180 до 180)
|
||||
- **Пароль**: минимум 8 символов, максимум 70 (для совместимости с bcrypt)
|
||||
- **Email**: валидный формат email
|
||||
- **Телефон**: рекомендуется международный формат (+1234567890)
|
||||
- **Сообщения**: максимум 500 символов для alert message, 200 для safety check
|
||||
- **Описания**: 10-1000 символов для emergency reports
|
||||
|
||||
### Обязательные поля
|
||||
- При регистрации: username, email, password
|
||||
- При создании оповещения: latitude, longitude, alert_type
|
||||
- При отклике: response_type
|
||||
- При обновлении местоположения: latitude, longitude
|
||||
|
||||
### Рекомендации по обработке ошибок
|
||||
```typescript
|
||||
interface APIError {
|
||||
detail: string;
|
||||
status_code: number;
|
||||
field_errors?: {
|
||||
[field: string]: string[];
|
||||
};
|
||||
}
|
||||
|
||||
// Обработка ошибок валидации
|
||||
try {
|
||||
await api.createAlert(alertData);
|
||||
} catch (error) {
|
||||
if (error.status_code === 422) {
|
||||
// Показать ошибки валидации для каждого поля
|
||||
Object.entries(error.field_errors || {}).forEach(([field, errors]) => {
|
||||
console.error(`${field}: ${errors.join(', ')}`);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Эти схемы данных обеспечивают полную интеграцию мобильного приложения с Women's Safety App API.
|
||||
600
docs/EMERGENCY_SERVICE_API.md
Normal file
600
docs/EMERGENCY_SERVICE_API.md
Normal file
@@ -0,0 +1,600 @@
|
||||
# Emergency Service API Документация
|
||||
|
||||
## Обзор
|
||||
Emergency Service предоставляет API для работы с экстренными ситуациями, включая создание оповещений, получение статистики и управление чрезвычайными ситуациями.
|
||||
|
||||
**Базовый URL:** `http://192.168.0.103:8002`
|
||||
**Аутентификация:** Bearer Token (JWT)
|
||||
|
||||
---
|
||||
|
||||
## Схемы данных (Schemas)
|
||||
|
||||
### 1. AlertType (Enum)
|
||||
Типы экстренных ситуаций:
|
||||
```python
|
||||
GENERAL = "general" # Общая помощь
|
||||
MEDICAL = "medical" # Медицинская помощь
|
||||
VIOLENCE = "violence" # Насилие
|
||||
HARASSMENT = "harassment" # Преследование
|
||||
UNSAFE_AREA = "unsafe_area" # Небезопасная зона
|
||||
ACCIDENT = "accident" # ДТП/авария
|
||||
FIRE = "fire" # Пожар
|
||||
NATURAL_DISASTER = "natural_disaster" # Стихийное бедствие
|
||||
```
|
||||
|
||||
### 2. AlertStatus (Enum)
|
||||
Статусы оповещений:
|
||||
```python
|
||||
ACTIVE = "active" # Активно
|
||||
RESOLVED = "resolved" # Решено
|
||||
CANCELLED = "cancelled" # Отменено
|
||||
INVESTIGATING = "investigating" # Расследуется
|
||||
```
|
||||
|
||||
### 3. ResponseType (Enum)
|
||||
Типы ответов на оповещения:
|
||||
```python
|
||||
HELP_ON_WAY = "help_on_way" # Помощь в пути
|
||||
CONTACTED_AUTHORITIES = "contacted_authorities" # Связались с властями
|
||||
SAFE_NOW = "safe_now" # Сейчас в безопасности
|
||||
FALSE_ALARM = "false_alarm" # Ложная тревога
|
||||
INVESTIGATING = "investigating" # Расследуется
|
||||
RESOLVED = "resolved" # Решено
|
||||
```
|
||||
|
||||
### 4. EmergencyAlertCreate
|
||||
Создание экстренного оповещения:
|
||||
```json
|
||||
{
|
||||
"latitude": 55.7558, // float, координата широты (-90 до 90)
|
||||
"longitude": 37.6176, // float, координата долготы (-180 до 180)
|
||||
"alert_type": "general", // AlertType, тип оповещения
|
||||
"message": "Описание ситуации", // string?, описание (макс 500 символов)
|
||||
"address": "Адрес места", // string?, адрес (макс 500 символов)
|
||||
"contact_emergency_services": true, // bool, связываться ли со службами экстренного реагирования
|
||||
"notify_emergency_contacts": true // bool, уведомлять ли экстренные контакты
|
||||
}
|
||||
```
|
||||
|
||||
### 5. EmergencyAlertResponse
|
||||
Ответ с данными оповещения:
|
||||
```json
|
||||
{
|
||||
"id": 123, // int, ID оповещения
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID оповещения
|
||||
"user_id": 26, // int, ID пользователя
|
||||
"latitude": 55.7558, // float, широта
|
||||
"longitude": 37.6176, // float, долгота
|
||||
"address": "Красная площадь, Москва", // string?, адрес
|
||||
"alert_type": "general", // AlertType, тип оповещения
|
||||
"status": "active", // AlertStatus, статус
|
||||
"message": "Нужна помощь", // string?, описание
|
||||
"is_resolved": false, // bool, решено ли
|
||||
"resolved_at": null, // datetime?, время решения
|
||||
"resolved_notes": null, // string?, заметки о решении
|
||||
"notified_users_count": 5, // int, количество уведомленных пользователей
|
||||
"responded_users_count": 2, // int, количество ответивших пользователей
|
||||
"created_at": "2024-01-15T10:30:00Z", // datetime, время создания
|
||||
"updated_at": "2024-01-15T10:35:00Z", // datetime?, время обновления
|
||||
"user_name": "Test User", // string?, имя пользователя
|
||||
"user_phone": "+1234567890" // string?, телефон пользователя
|
||||
}
|
||||
```
|
||||
|
||||
### 6. EmergencyResponseCreate
|
||||
Создание ответа на оповещение:
|
||||
```json
|
||||
{
|
||||
"response_type": "help_on_way", // ResponseType, тип ответа
|
||||
"message": "Еду к вам!", // string?, сообщение (макс 500 символов)
|
||||
"eta_minutes": 15, // int?, предполагаемое время прибытия в минутах (0-240)
|
||||
"location_sharing": true // bool, делиться ли местоположением
|
||||
}
|
||||
```
|
||||
|
||||
### 7. EmergencyResponseResponse
|
||||
Ответ с данными отклика:
|
||||
```json
|
||||
{
|
||||
"id": 456, // int, ID отклика
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID отклика
|
||||
"alert_id": 123, // int, ID оповещения
|
||||
"user_id": 27, // int, ID откликнувшегося пользователя
|
||||
"response_type": "help_on_way", // ResponseType, тип отклика
|
||||
"message": "Еду к вам!", // string?, сообщение
|
||||
"eta_minutes": 15, // int?, время прибытия
|
||||
"location_sharing": true, // bool, делиться местоположением
|
||||
"created_at": "2024-01-15T10:32:00Z", // datetime, время создания
|
||||
"responder_name": "Helper User", // string?, имя откликнувшегося
|
||||
"responder_phone": "+9876543210" // string?, телефон откликнувшегося
|
||||
}
|
||||
```
|
||||
|
||||
### 8. EmergencyReportCreate
|
||||
Создание отчета о происшествии:
|
||||
```json
|
||||
{
|
||||
"latitude": 55.7558, // float, широта (-90 до 90)
|
||||
"longitude": 37.6176, // float, долгота (-180 до 180)
|
||||
"report_type": "violence", // string, тип происшествия
|
||||
"description": "Детальное описание происшествия...", // string, описание (10-1000 символов)
|
||||
"address": "Адрес происшествия", // string?, адрес (макс 500 символов)
|
||||
"is_anonymous": false, // bool, анонимный отчет
|
||||
"severity": 4 // int, серьезность от 1 до 5
|
||||
}
|
||||
```
|
||||
|
||||
### 9. EmergencyReportResponse
|
||||
Ответ с данными отчета:
|
||||
```json
|
||||
{
|
||||
"id": 789, // int, ID отчета
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID отчета
|
||||
"user_id": 26, // int?, ID пользователя (null для анонимных)
|
||||
"latitude": 55.7558, // float, широта
|
||||
"longitude": 37.6176, // float, долгота
|
||||
"address": "Красная площадь", // string?, адрес
|
||||
"report_type": "violence", // string, тип происшествия
|
||||
"description": "Описание...", // string, описание
|
||||
"is_anonymous": false, // bool, анонимный ли
|
||||
"severity": 4, // int, серьезность (1-5)
|
||||
"status": "pending", // string, статус (pending/investigating/resolved)
|
||||
"created_at": "2024-01-15T10:45:00Z" // datetime, время создания
|
||||
}
|
||||
```
|
||||
|
||||
### 10. SafetyCheckCreate
|
||||
Создание отметки безопасности:
|
||||
```json
|
||||
{
|
||||
"message": "Я в порядке", // string?, сообщение (макс 200 символов)
|
||||
"location_latitude": 55.7558, // float?, широта (-90 до 90)
|
||||
"location_longitude": 37.6176 // float?, долгота (-180 до 180)
|
||||
}
|
||||
```
|
||||
|
||||
### 11. SafetyCheckResponse
|
||||
Ответ с данными отметки безопасности:
|
||||
```json
|
||||
{
|
||||
"id": 101, // int, ID отметки
|
||||
"uuid": "8ed4fb51-8a90-4b22...", // string, UUID отметки
|
||||
"user_id": 26, // int, ID пользователя
|
||||
"message": "Я в порядке", // string?, сообщение
|
||||
"location_latitude": 55.7558, // float?, широта
|
||||
"location_longitude": 37.6176, // float?, долгота
|
||||
"created_at": "2024-01-15T11:00:00Z" // datetime, время создания
|
||||
}
|
||||
```
|
||||
|
||||
### 12. EmergencyStatistics
|
||||
Статистика экстренных ситуаций:
|
||||
```json
|
||||
{
|
||||
"total_alerts": 150, // int, общее количество оповещений
|
||||
"active_alerts": 12, // int, активные оповещения
|
||||
"resolved_alerts": 138, // int, решенные оповещения
|
||||
"total_responders": 89, // int, общее количество откликнувшихся
|
||||
"avg_response_time_minutes": 8.5 // float, среднее время отклика в минутах
|
||||
}
|
||||
```
|
||||
|
||||
### 13. NearbyAlertResponse
|
||||
Ближайшие оповещения:
|
||||
```json
|
||||
{
|
||||
"id": 123, // int, ID оповещения
|
||||
"alert_type": "medical", // string, тип оповещения
|
||||
"latitude": 55.7558, // float, широта
|
||||
"longitude": 37.6176, // float, долгота
|
||||
"address": "Больница №1", // string?, адрес
|
||||
"distance_km": 2.5, // float, расстояние в километрах
|
||||
"created_at": "2024-01-15T09:15:00Z", // datetime, время создания
|
||||
"responded_users_count": 3 // int, количество откликов
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### 1. Проверка здоровья сервиса
|
||||
|
||||
**GET** `/health`
|
||||
|
||||
**Описание:** Проверка статуса работы сервиса
|
||||
**Авторизация:** Не требуется
|
||||
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"status": "healthy",
|
||||
"service": "emergency_service"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### 2. Создание экстренного оповещения
|
||||
|
||||
**POST** `/api/v1/alert`
|
||||
|
||||
**Описание:** Создание нового экстренного оповещения
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Тело запроса:** `EmergencyAlertCreate`
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X POST http://192.168.0.103:8002/api/v1/alert \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"latitude": 55.7558,
|
||||
"longitude": 37.6176,
|
||||
"alert_type": "general",
|
||||
"message": "Нужна помощь, подозрительная активность",
|
||||
"address": "Красная площадь, Москва"
|
||||
}'
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `EmergencyAlertResponse`
|
||||
|
||||
---
|
||||
|
||||
### 3. Ответить на оповещение
|
||||
|
||||
**POST** `/api/v1/alert/{alert_id}/respond`
|
||||
|
||||
**Описание:** Отправка отклика на экстренное оповещение
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Параметры URL:**
|
||||
- `alert_id` (int) - ID оповещения
|
||||
|
||||
**Тело запроса:** `EmergencyResponseCreate`
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X POST http://192.168.0.103:8002/api/v1/alert/123/respond \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"response_type": "help_on_way",
|
||||
"message": "Еду к вам, буду через 10 минут!",
|
||||
"eta_minutes": 10
|
||||
}'
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `EmergencyResponseResponse`
|
||||
|
||||
**Ошибки:**
|
||||
- `404` - Оповещение не найдено
|
||||
- `400` - Пользователь уже откликнулся на это оповещение
|
||||
|
||||
---
|
||||
|
||||
### 4. Решить оповещение
|
||||
|
||||
**PUT** `/api/v1/alert/{alert_id}/resolve`
|
||||
|
||||
**Описание:** Помечает оповещение как решенное
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Параметры URL:**
|
||||
- `alert_id` (int) - ID оповещения
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X PUT http://192.168.0.103:8002/api/v1/alert/123/resolve \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
```json
|
||||
{
|
||||
"message": "Alert resolved successfully"
|
||||
}
|
||||
```
|
||||
|
||||
**Ошибки:**
|
||||
- `404` - Оповещение не найдено
|
||||
- `403` - Только создатель может решить оповещение
|
||||
|
||||
---
|
||||
|
||||
### 5. Обновить оповещение
|
||||
|
||||
**PUT** `/api/v1/alert/{alert_id}`
|
||||
|
||||
**Описание:** Обновление существующего оповещения
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Параметры URL:**
|
||||
- `alert_id` (int) - ID оповещения
|
||||
|
||||
**Тело запроса:** `EmergencyAlertUpdate`
|
||||
```json
|
||||
{
|
||||
"status": "resolved", // AlertStatus?, новый статус
|
||||
"message": "Обновленное описание", // string?, новое описание
|
||||
"resolved_notes": "Помощь получена" // string?, заметки о решении
|
||||
}
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `EmergencyAlertResponse`
|
||||
|
||||
---
|
||||
|
||||
### 6. Получить мои оповещения
|
||||
|
||||
**GET** `/api/v1/alerts/my`
|
||||
|
||||
**Описание:** Получение оповещений текущего пользователя
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X GET http://192.168.0.103:8002/api/v1/alerts/my \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `List[EmergencyAlertResponse]`
|
||||
|
||||
---
|
||||
|
||||
### 7. Получить активные оповещения
|
||||
|
||||
**GET** `/api/v1/alerts/active`
|
||||
|
||||
**Описание:** Получение всех активных оповещений
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `List[EmergencyAlertResponse]`
|
||||
|
||||
---
|
||||
|
||||
### 8. Получить ближайшие оповещения
|
||||
|
||||
**GET** `/api/v1/alerts/nearby`
|
||||
|
||||
**Описание:** Поиск оповещений в указанном радиусе
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Query параметры:**
|
||||
- `latitude` (float, обязательный) - Широта (-90 до 90)
|
||||
- `longitude` (float, обязательный) - Долгота (-180 до 180)
|
||||
- `radius_km` (float, опциональный) - Радиус поиска в км (по умолчанию 10.0, макс 100.0)
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X GET "http://192.168.0.103:8002/api/v1/alerts/nearby?latitude=55.7558&longitude=37.6176&radius_km=5.0" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `List[NearbyAlertResponse]` (отсортированы по расстоянию)
|
||||
|
||||
---
|
||||
|
||||
### 9. Получить отклики на оповещение
|
||||
|
||||
**GET** `/api/v1/alert/{alert_id}/responses`
|
||||
|
||||
**Описание:** Получение всех откликов на конкретное оповещение
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Параметры URL:**
|
||||
- `alert_id` (int) - ID оповещения
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `List[EmergencyResponseResponse]`
|
||||
|
||||
---
|
||||
|
||||
### 10. Создать отчет о происшествии
|
||||
|
||||
**POST** `/api/v1/report`
|
||||
|
||||
**Описание:** Создание отчета о происшествии
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Тело запроса:** `EmergencyReportCreate`
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X POST http://192.168.0.103:8002/api/v1/report \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"latitude": 55.7558,
|
||||
"longitude": 37.6176,
|
||||
"report_type": "harassment",
|
||||
"description": "Преследование в районе метро...",
|
||||
"severity": 4,
|
||||
"is_anonymous": false
|
||||
}'
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `EmergencyReportResponse`
|
||||
|
||||
---
|
||||
|
||||
### 11. Получить отчеты
|
||||
|
||||
**GET** `/api/v1/reports`
|
||||
|
||||
**Описание:** Получение списка отчетов о происшествиях
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `List[EmergencyReportResponse]`
|
||||
|
||||
---
|
||||
|
||||
### 12. Создать отметку безопасности
|
||||
|
||||
**POST** `/api/v1/safety-check`
|
||||
|
||||
**Описание:** Отметка о том, что пользователь в безопасности
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Тело запроса:** `SafetyCheckCreate`
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X POST http://192.168.0.103:8002/api/v1/safety-check \
|
||||
-H "Content-Type: application/json" \
|
||||
-H "Authorization: Bearer YOUR_TOKEN" \
|
||||
-d '{
|
||||
"message": "Добрался домой, все хорошо",
|
||||
"location_latitude": 55.7558,
|
||||
"location_longitude": 37.6176
|
||||
}'
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `SafetyCheckResponse`
|
||||
|
||||
---
|
||||
|
||||
### 13. Получить отметки безопасности
|
||||
|
||||
**GET** `/api/v1/safety-checks`
|
||||
|
||||
**Описание:** Получение отметок безопасности пользователя
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `List[SafetyCheckResponse]`
|
||||
|
||||
---
|
||||
|
||||
### 14. Получить статистику
|
||||
|
||||
**GET** `/api/v1/stats`
|
||||
|
||||
**Описание:** Получение статистики по экстренным ситуациям
|
||||
**Авторизация:** Bearer Token
|
||||
|
||||
**Пример запроса:**
|
||||
```bash
|
||||
curl -X GET http://192.168.0.103:8002/api/v1/stats \
|
||||
-H "Authorization: Bearer YOUR_TOKEN"
|
||||
```
|
||||
|
||||
**Успешный ответ:** `200 OK`
|
||||
**Тело ответа:** `EmergencyStatistics`
|
||||
|
||||
---
|
||||
|
||||
## Коды ошибок
|
||||
|
||||
### Общие ошибки
|
||||
- `400 Bad Request` - Неверные данные в запросе
|
||||
- `401 Unauthorized` - Требуется авторизация
|
||||
- `403 Forbidden` - Недостаточно прав
|
||||
- `404 Not Found` - Ресурс не найден
|
||||
- `422 Unprocessable Entity` - Ошибка валидации данных
|
||||
- `500 Internal Server Error` - Внутренняя ошибка сервера
|
||||
|
||||
### Специфические ошибки
|
||||
- `400` - "You have already responded to this alert" (при повторном отклике)
|
||||
- `403` - "Only alert creator can resolve/update the alert" (при попытке изменения чужого оповещения)
|
||||
- `404` - "Alert not found" (оповещение не найдено)
|
||||
|
||||
---
|
||||
|
||||
## Примеры интеграции с мобильным приложением
|
||||
|
||||
### 1. Создание экстренного оповещения
|
||||
```javascript
|
||||
const createEmergencyAlert = async (alertData) => {
|
||||
const response = await fetch('http://192.168.0.103:8002/api/v1/alert', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${userToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
latitude: alertData.latitude,
|
||||
longitude: alertData.longitude,
|
||||
alert_type: alertData.type || 'general',
|
||||
message: alertData.message,
|
||||
address: alertData.address
|
||||
})
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Получение ближайших оповещений
|
||||
```javascript
|
||||
const getNearbyAlerts = async (latitude, longitude, radiusKm = 10) => {
|
||||
const params = new URLSearchParams({
|
||||
latitude: latitude.toString(),
|
||||
longitude: longitude.toString(),
|
||||
radius_km: radiusKm.toString()
|
||||
});
|
||||
|
||||
const response = await fetch(
|
||||
`http://192.168.0.103:8002/api/v1/alerts/nearby?${params}`,
|
||||
{
|
||||
headers: {
|
||||
'Authorization': `Bearer ${userToken}`
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Отклик на оповещение
|
||||
```javascript
|
||||
const respondToAlert = async (alertId, responseData) => {
|
||||
const response = await fetch(
|
||||
`http://192.168.0.103:8002/api/v1/alert/${alertId}/respond`,
|
||||
{
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${userToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
response_type: responseData.type,
|
||||
message: responseData.message,
|
||||
eta_minutes: responseData.etaMinutes
|
||||
})
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## Дополнительные возможности
|
||||
|
||||
### WebSocket подключения (планируется)
|
||||
Для получения уведомлений в реальном времени о новых оповещениях и откликах.
|
||||
|
||||
### Push уведомления
|
||||
API интегрируется с Notification Service для отправки push уведомлений на мобильные устройства.
|
||||
|
||||
### Геолокация
|
||||
Все координаты используют WGS84 (стандарт GPS). Расстояния вычисляются по формуле гаверсинусов.
|
||||
|
||||
### Безопасность
|
||||
- Все данные о местоположении шифруются
|
||||
- Анонимные отчеты не содержат идентификационных данных
|
||||
- JWT токены имеют ограниченный срок действия
|
||||
495
docs/MOBILE_API_SPECS.md
Normal file
495
docs/MOBILE_API_SPECS.md
Normal file
@@ -0,0 +1,495 @@
|
||||
# API Спецификации для мобильного приложения Women's Safety App
|
||||
|
||||
## Обзор системы
|
||||
Микросервисная архитектура с 6 сервисами:
|
||||
- **API Gateway** (порт 8000) - точка входа
|
||||
- **User Service** (порт 8001) - управление пользователями
|
||||
- **Emergency Service** (порт 8002) - экстренные ситуации
|
||||
- **Location Service** (порт 8003) - геолокация
|
||||
- **Calendar Service** (порт 8004) - календарь здоровья
|
||||
- **Notification Service** (порт 8005) - уведомления
|
||||
|
||||
**Базовый URL:** `http://192.168.0.103:8000` (API Gateway)
|
||||
**Прямые сервисы:** `http://192.168.0.103:800X` (где X - номер сервиса)
|
||||
|
||||
---
|
||||
|
||||
## 🔐 Система авторизации
|
||||
|
||||
### Регистрация пользователя
|
||||
**POST** `/api/v1/auth/register`
|
||||
```json
|
||||
{
|
||||
"username": "testuser",
|
||||
"email": "test@example.com",
|
||||
"password": "password123",
|
||||
"full_name": "Test User",
|
||||
"phone": "+1234567890"
|
||||
}
|
||||
```
|
||||
|
||||
### Вход в систему
|
||||
**POST** `/api/v1/auth/login`
|
||||
```json
|
||||
{
|
||||
"username": "testuser", // или "email": "test@example.com"
|
||||
"password": "password123"
|
||||
}
|
||||
```
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"access_token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
|
||||
"token_type": "bearer"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 👤 User Service API
|
||||
|
||||
### Профиль пользователя
|
||||
**GET** `/api/v1/user/profile`
|
||||
**Ответ:** Полная информация о пользователе
|
||||
|
||||
**PUT** `/api/v1/user/profile` - Обновление профиля
|
||||
|
||||
### Дашборд пользователя
|
||||
**GET** `/api/v1/user/dashboard`
|
||||
**Ответ:**
|
||||
```json
|
||||
{
|
||||
"user_info": {...},
|
||||
"recent_alerts": [...],
|
||||
"emergency_contacts": [...],
|
||||
"safety_stats": {...}
|
||||
}
|
||||
```
|
||||
|
||||
### Экстренные контакты
|
||||
**GET** `/api/v1/user/emergency-contacts` - Список контактов
|
||||
**POST** `/api/v1/user/emergency-contacts` - Добавить контакт
|
||||
**PUT** `/api/v1/user/emergency-contacts/{id}` - Обновить контакт
|
||||
**DELETE** `/api/v1/user/emergency-contacts/{id}` - Удалить контакт
|
||||
|
||||
**Схема контакта:**
|
||||
```json
|
||||
{
|
||||
"name": "Мама",
|
||||
"phone_number": "+1234567890",
|
||||
"relationship": "mother",
|
||||
"notes": "Основной контакт",
|
||||
"is_active": true
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🆘 Emergency Service API (Полная спецификация)
|
||||
|
||||
### Типы данных
|
||||
```typescript
|
||||
enum AlertType {
|
||||
GENERAL = "general",
|
||||
MEDICAL = "medical",
|
||||
VIOLENCE = "violence",
|
||||
HARASSMENT = "harassment",
|
||||
UNSAFE_AREA = "unsafe_area",
|
||||
ACCIDENT = "accident",
|
||||
FIRE = "fire",
|
||||
NATURAL_DISASTER = "natural_disaster"
|
||||
}
|
||||
|
||||
enum AlertStatus {
|
||||
ACTIVE = "active",
|
||||
RESOLVED = "resolved",
|
||||
CANCELLED = "cancelled",
|
||||
INVESTIGATING = "investigating"
|
||||
}
|
||||
|
||||
enum ResponseType {
|
||||
HELP_ON_WAY = "help_on_way",
|
||||
CONTACTED_AUTHORITIES = "contacted_authorities",
|
||||
SAFE_NOW = "safe_now",
|
||||
FALSE_ALARM = "false_alarm"
|
||||
}
|
||||
```
|
||||
|
||||
### Основные endpoints
|
||||
|
||||
#### 1. Создание экстренного оповещения
|
||||
**POST** `/api/v1/alert` (через Emergency Service напрямую: порт 8002)
|
||||
```json
|
||||
{
|
||||
"latitude": 55.7558,
|
||||
"longitude": 37.6176,
|
||||
"alert_type": "general",
|
||||
"message": "Нужна помощь",
|
||||
"address": "Красная площадь, Москва",
|
||||
"contact_emergency_services": true,
|
||||
"notify_emergency_contacts": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 2. Получение оповещений
|
||||
- **GET** `/api/v1/alerts/my` - Мои оповещения
|
||||
- **GET** `/api/v1/alerts/active` - Активные оповещения
|
||||
- **GET** `/api/v1/alerts/nearby?latitude=55.7558&longitude=37.6176&radius_km=10` - Ближайшие
|
||||
|
||||
#### 3. Отклик на оповещение
|
||||
**POST** `/api/v1/alert/{alert_id}/respond`
|
||||
```json
|
||||
{
|
||||
"response_type": "help_on_way",
|
||||
"message": "Еду к вам!",
|
||||
"eta_minutes": 15,
|
||||
"location_sharing": true
|
||||
}
|
||||
```
|
||||
|
||||
#### 4. Управление оповещением
|
||||
- **PUT** `/api/v1/alert/{alert_id}` - Обновить оповещение
|
||||
- **PUT** `/api/v1/alert/{alert_id}/resolve` - Пометить как решенное
|
||||
- **GET** `/api/v1/alert/{alert_id}/responses` - Получить отклики
|
||||
|
||||
#### 5. Отчеты о происшествиях
|
||||
**POST** `/api/v1/report`
|
||||
```json
|
||||
{
|
||||
"latitude": 55.7558,
|
||||
"longitude": 37.6176,
|
||||
"report_type": "harassment",
|
||||
"description": "Описание происшествия",
|
||||
"severity": 4,
|
||||
"is_anonymous": false
|
||||
}
|
||||
```
|
||||
|
||||
#### 6. Отметки безопасности
|
||||
**POST** `/api/v1/safety-check`
|
||||
```json
|
||||
{
|
||||
"message": "Я в безопасности",
|
||||
"location_latitude": 55.7558,
|
||||
"location_longitude": 37.6176
|
||||
}
|
||||
```
|
||||
|
||||
#### 7. Статистика
|
||||
**GET** `/api/v1/stats`
|
||||
```json
|
||||
{
|
||||
"total_alerts": 150,
|
||||
"active_alerts": 12,
|
||||
"resolved_alerts": 138,
|
||||
"total_responders": 89,
|
||||
"avg_response_time_minutes": 8.5
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📍 Location Service API
|
||||
|
||||
### Обновление местоположения
|
||||
**POST** `/api/v1/location/update`
|
||||
```json
|
||||
{
|
||||
"latitude": 55.7558,
|
||||
"longitude": 37.6176,
|
||||
"accuracy": 10.0,
|
||||
"altitude": 150.0
|
||||
}
|
||||
```
|
||||
|
||||
### Поиск пользователей рядом
|
||||
**GET** `/api/v1/nearby-users?latitude=55.7558&longitude=37.6176&radius_km=1.0`
|
||||
|
||||
### Геокодирование
|
||||
**GET** `/api/v1/geocode/reverse?latitude=55.7558&longitude=37.6176`
|
||||
**GET** `/api/v1/geocode/forward?address=Красная площадь, Москва`
|
||||
|
||||
---
|
||||
|
||||
## 📅 Calendar Service API
|
||||
|
||||
### Календарь здоровья
|
||||
**GET** `/api/v1/calendar/entries` - Записи календаря
|
||||
**POST** `/api/v1/calendar/entries` - Добавить запись
|
||||
|
||||
**Схема записи:**
|
||||
```json
|
||||
{
|
||||
"date": "2024-01-15",
|
||||
"entry_type": "period_start", // period_start, period_end, mood, symptoms
|
||||
"notes": "Заметки",
|
||||
"mood_score": 4, // 1-5
|
||||
"symptoms": ["headache", "fatigue"]
|
||||
}
|
||||
```
|
||||
|
||||
### Аналитика
|
||||
**GET** `/api/v1/calendar/analytics` - Аналитика цикла
|
||||
|
||||
---
|
||||
|
||||
## 🔔 Notification Service API
|
||||
|
||||
### Отправка уведомления
|
||||
**POST** `/api/v1/send-notification`
|
||||
```json
|
||||
{
|
||||
"user_id": 123,
|
||||
"title": "Экстренное оповещение",
|
||||
"message": "Новое оповещение рядом с вами",
|
||||
"type": "emergency_alert",
|
||||
"data": {"alert_id": 456}
|
||||
}
|
||||
```
|
||||
|
||||
### Push токены
|
||||
**POST** `/api/v1/push-token`
|
||||
```json
|
||||
{
|
||||
"token": "fcm_or_apns_token",
|
||||
"platform": "ios" // или "android"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔗 Интеграция для мобильного приложения
|
||||
|
||||
### Базовый HTTP клиент
|
||||
```javascript
|
||||
class WomenSafetyAPI {
|
||||
constructor(baseUrl = 'http://192.168.0.103:8000') {
|
||||
this.baseUrl = baseUrl;
|
||||
this.token = null;
|
||||
}
|
||||
|
||||
setToken(token) {
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
async request(method, endpoint, data = null) {
|
||||
const config = {
|
||||
method,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
};
|
||||
|
||||
if (this.token) {
|
||||
config.headers.Authorization = `Bearer ${this.token}`;
|
||||
}
|
||||
|
||||
if (data && (method === 'POST' || method === 'PUT')) {
|
||||
config.body = JSON.stringify(data);
|
||||
}
|
||||
|
||||
const url = `${this.baseUrl}${endpoint}`;
|
||||
const response = await fetch(url, config);
|
||||
|
||||
if (!response.ok) {
|
||||
const error = await response.json();
|
||||
throw new Error(error.detail || 'API Error');
|
||||
}
|
||||
|
||||
return await response.json();
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Основные методы API
|
||||
```javascript
|
||||
class EmergencyAPI extends WomenSafetyAPI {
|
||||
// Авторизация
|
||||
async login(username, password) {
|
||||
const response = await this.request('POST', '/api/v1/auth/login', {
|
||||
username,
|
||||
password
|
||||
});
|
||||
this.setToken(response.access_token);
|
||||
return response;
|
||||
}
|
||||
|
||||
async register(userData) {
|
||||
return await this.request('POST', '/api/v1/auth/register', userData);
|
||||
}
|
||||
|
||||
// Экстренные ситуации (через порт 8002)
|
||||
async createAlert(alertData) {
|
||||
const emergencyUrl = this.baseUrl.replace('8000', '8002');
|
||||
return await fetch(`${emergencyUrl}/api/v1/alert`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
body: JSON.stringify(alertData)
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
async getNearbyAlerts(latitude, longitude, radiusKm = 10) {
|
||||
const emergencyUrl = this.baseUrl.replace('8000', '8002');
|
||||
const params = new URLSearchParams({
|
||||
latitude: latitude.toString(),
|
||||
longitude: longitude.toString(),
|
||||
radius_km: radiusKm.toString()
|
||||
});
|
||||
|
||||
return await fetch(`${emergencyUrl}/api/v1/alerts/nearby?${params}`, {
|
||||
headers: { 'Authorization': `Bearer ${this.token}` }
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
async respondToAlert(alertId, responseData) {
|
||||
const emergencyUrl = this.baseUrl.replace('8000', '8002');
|
||||
return await fetch(`${emergencyUrl}/api/v1/alert/${alertId}/respond`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
body: JSON.stringify(responseData)
|
||||
}).then(r => r.json());
|
||||
}
|
||||
|
||||
// Профиль пользователя
|
||||
async getProfile() {
|
||||
return await this.request('GET', '/api/v1/user/profile');
|
||||
}
|
||||
|
||||
async getDashboard() {
|
||||
return await this.request('GET', '/api/v1/user/dashboard');
|
||||
}
|
||||
|
||||
// Экстренные контакты
|
||||
async getEmergencyContacts() {
|
||||
return await this.request('GET', '/api/v1/user/emergency-contacts');
|
||||
}
|
||||
|
||||
async addEmergencyContact(contactData) {
|
||||
return await this.request('POST', '/api/v1/user/emergency-contacts', contactData);
|
||||
}
|
||||
|
||||
// Местоположение
|
||||
async updateLocation(locationData) {
|
||||
return await this.request('POST', '/api/v1/location/update', locationData);
|
||||
}
|
||||
|
||||
// Отметка безопасности
|
||||
async createSafetyCheck(safetyData) {
|
||||
const emergencyUrl = this.baseUrl.replace('8000', '8002');
|
||||
return await fetch(`${emergencyUrl}/api/v1/safety-check`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${this.token}`
|
||||
},
|
||||
body: JSON.stringify(safetyData)
|
||||
}).then(r => r.json());
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Примеры использования
|
||||
```javascript
|
||||
// Инициализация
|
||||
const api = new EmergencyAPI('http://192.168.0.103:8000');
|
||||
|
||||
// Авторизация
|
||||
await api.login('testuser', 'password123');
|
||||
|
||||
// Создание экстренного оповещения
|
||||
const alert = await api.createAlert({
|
||||
latitude: 55.7558,
|
||||
longitude: 37.6176,
|
||||
alert_type: 'general',
|
||||
message: 'Нужна помощь!',
|
||||
address: 'Красная площадь, Москва'
|
||||
});
|
||||
|
||||
// Поиск ближайших оповещений
|
||||
const nearbyAlerts = await api.getNearbyAlerts(55.7558, 37.6176, 5);
|
||||
|
||||
// Отклик на оповещение
|
||||
const response = await api.respondToAlert(alert.id, {
|
||||
response_type: 'help_on_way',
|
||||
message: 'Еду к вам!',
|
||||
eta_minutes: 10
|
||||
});
|
||||
|
||||
// Получение профиля
|
||||
const profile = await api.getProfile();
|
||||
|
||||
// Отметка безопасности
|
||||
const safetyCheck = await api.createSafetyCheck({
|
||||
message: 'Добрался домой безопасно',
|
||||
location_latitude: 55.7600,
|
||||
location_longitude: 37.6100
|
||||
});
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🛡️ Безопасность и аутентификация
|
||||
|
||||
### JWT токены
|
||||
- Время жизни: настраивается в конфигурации
|
||||
- Формат: `Bearer {token}`
|
||||
- Включают: user_id, email, exp (время истечения)
|
||||
|
||||
### Заголовки запросов
|
||||
```
|
||||
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
|
||||
Content-Type: application/json
|
||||
```
|
||||
|
||||
### Коды ошибок
|
||||
- `401 Unauthorized` - Неверные учетные данные
|
||||
- `403 Forbidden` - Недостаточно прав
|
||||
- `422 Unprocessable Entity` - Ошибки валидации
|
||||
- `500 Internal Server Error` - Внутренняя ошибка
|
||||
|
||||
---
|
||||
|
||||
## 📱 Рекомендации для мобильной разработки
|
||||
|
||||
### Push уведомления
|
||||
1. Зарегистрируйте push токен через `/api/v1/push-token`
|
||||
2. Обрабатывайте входящие уведомления для экстренных ситуаций
|
||||
3. Показывайте локальные уведомления при получении emergency_alert
|
||||
|
||||
### Геолокация
|
||||
1. Запрашивайте разрешения на точное местоположение
|
||||
2. Обновляйте координаты через `/api/v1/location/update`
|
||||
3. Кэшируйте последнее известное местоположение
|
||||
|
||||
### Оффлайн режим
|
||||
1. Кэшируйте экстренные контакты локально
|
||||
2. Сохраняйте черновики оповещений для отправки при восстановлении связи
|
||||
3. Показывайте статус подключения к сервису
|
||||
|
||||
### UI/UX
|
||||
1. Красная кнопка SOS должна быть всегда доступна
|
||||
2. Быстрый доступ к созданию оповещения (виджет, ярлык)
|
||||
3. Показывайте статус отправленных оповещений
|
||||
4. Уведомления о новых откликах на оповещения
|
||||
|
||||
---
|
||||
|
||||
## 🧪 Тестирование
|
||||
|
||||
Используйте прилагаемый скрипт `test_emergency_api.sh` для полного тестирования API:
|
||||
```bash
|
||||
./test_emergency_api.sh
|
||||
```
|
||||
|
||||
Скрипт проверит:
|
||||
- Регистрацию и авторизацию
|
||||
- Создание и управление оповещениями
|
||||
- Отклики на оповещения
|
||||
- Отчеты и отметки безопасности
|
||||
- Статистику и аналитику
|
||||
589
docs/MOBILE_INTEGRATION_EXAMPLES.md
Normal file
589
docs/MOBILE_INTEGRATION_EXAMPLES.md
Normal file
@@ -0,0 +1,589 @@
|
||||
# Примеры использования API для мобильной разработки
|
||||
|
||||
## 🚀 Быстрый старт
|
||||
|
||||
### 1. Авторизация пользователя
|
||||
```javascript
|
||||
// Регистрация
|
||||
const registerUser = async (userData) => {
|
||||
const response = await fetch('http://192.168.0.103:8000/api/auth/register', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({
|
||||
username: userData.username,
|
||||
email: userData.email,
|
||||
password: userData.password,
|
||||
full_name: userData.full_name,
|
||||
phone: userData.phone
|
||||
})
|
||||
});
|
||||
return await response.json();
|
||||
};
|
||||
|
||||
// Вход в систему
|
||||
const loginUser = async (username, password) => {
|
||||
const response = await fetch('http://192.168.0.103:8000/api/auth/login', {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ username, password })
|
||||
});
|
||||
const data = await response.json();
|
||||
|
||||
// Сохранить токен для дальнейших запросов
|
||||
localStorage.setItem('access_token', data.access_token);
|
||||
return data;
|
||||
};
|
||||
```
|
||||
|
||||
### 2. Создание SOS оповещения
|
||||
```javascript
|
||||
const createEmergencyAlert = async (location, alertType, message) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const response = await fetch('http://192.168.0.103:8000/api/emergency/alerts', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
latitude: location.latitude,
|
||||
longitude: location.longitude,
|
||||
alert_type: alertType, // 'general', 'medical', 'violence', etc.
|
||||
message: message,
|
||||
contact_emergency_services: true,
|
||||
notify_emergency_contacts: true
|
||||
})
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 3. Поиск ближайших оповещений
|
||||
```javascript
|
||||
const findNearbyAlerts = async (userLocation, radiusKm = 5) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const response = await fetch(
|
||||
`http://192.168.0.103:8000/api/emergency/nearby?latitude=${userLocation.latitude}&longitude=${userLocation.longitude}&radius_km=${radiusKm}`,
|
||||
{
|
||||
headers: { 'Authorization': `Bearer ${token}` }
|
||||
}
|
||||
);
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
### 4. Отклик на оповещение
|
||||
```javascript
|
||||
const respondToAlert = async (alertId, responseType, message, eta) => {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
const response = await fetch(`http://192.168.0.103:8000/api/emergency/alerts/${alertId}/responses`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
response_type: responseType, // 'help_on_way', 'contacted_authorities', etc.
|
||||
message: message,
|
||||
eta_minutes: eta,
|
||||
location_sharing: true
|
||||
})
|
||||
});
|
||||
|
||||
return await response.json();
|
||||
};
|
||||
```
|
||||
|
||||
## 📱 React Native компоненты
|
||||
|
||||
### SOS Button Component
|
||||
```jsx
|
||||
import React, { useState } from 'react';
|
||||
import { View, TouchableOpacity, Text, Alert } from 'react-native';
|
||||
import Geolocation from '@react-native-community/geolocation';
|
||||
|
||||
const SOSButton = ({ onEmergencyCreated }) => {
|
||||
const [isCreating, setIsCreating] = useState(false);
|
||||
|
||||
const handleSOSPress = () => {
|
||||
Alert.alert(
|
||||
'Экстренная ситуация',
|
||||
'Вы уверены, что хотите отправить SOS сигнал?',
|
||||
[
|
||||
{ text: 'Отмена', style: 'cancel' },
|
||||
{ text: 'SOS', style: 'destructive', onPress: createEmergencyAlert }
|
||||
]
|
||||
);
|
||||
};
|
||||
|
||||
const createEmergencyAlert = () => {
|
||||
setIsCreating(true);
|
||||
|
||||
Geolocation.getCurrentPosition(
|
||||
async (position) => {
|
||||
try {
|
||||
const alert = await createEmergencyAlert(
|
||||
{
|
||||
latitude: position.coords.latitude,
|
||||
longitude: position.coords.longitude
|
||||
},
|
||||
'general',
|
||||
'Экстренная помощь необходима!'
|
||||
);
|
||||
|
||||
onEmergencyCreated(alert);
|
||||
Alert.alert('Успешно', 'SOS сигнал отправлен');
|
||||
} catch (error) {
|
||||
Alert.alert('Ошибка', 'Не удалось отправить SOS сигнал');
|
||||
}
|
||||
setIsCreating(false);
|
||||
},
|
||||
(error) => {
|
||||
Alert.alert('Ошибка', 'Не удалось получить местоположение');
|
||||
setIsCreating(false);
|
||||
}
|
||||
);
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={{ alignItems: 'center', margin: 20 }}>
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
width: 120,
|
||||
height: 120,
|
||||
borderRadius: 60,
|
||||
backgroundColor: isCreating ? '#ccc' : '#ff4444',
|
||||
justifyContent: 'center',
|
||||
alignItems: 'center',
|
||||
elevation: 5
|
||||
}}
|
||||
onPress={handleSOSPress}
|
||||
disabled={isCreating}
|
||||
>
|
||||
<Text style={{ color: 'white', fontSize: 18, fontWeight: 'bold' }}>
|
||||
{isCreating ? 'Отправка...' : 'SOS'}
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
### Nearby Alerts List
|
||||
```jsx
|
||||
import React, { useEffect, useState } from 'react';
|
||||
import { View, FlatList, Text, TouchableOpacity } from 'react-native';
|
||||
|
||||
const NearbyAlertsList = ({ userLocation }) => {
|
||||
const [alerts, setAlerts] = useState([]);
|
||||
const [loading, setLoading] = useState(false);
|
||||
|
||||
useEffect(() => {
|
||||
if (userLocation) {
|
||||
loadNearbyAlerts();
|
||||
}
|
||||
}, [userLocation]);
|
||||
|
||||
const loadNearbyAlerts = async () => {
|
||||
setLoading(true);
|
||||
try {
|
||||
const nearbyAlerts = await findNearbyAlerts(userLocation);
|
||||
setAlerts(nearbyAlerts);
|
||||
} catch (error) {
|
||||
console.error('Ошибка загрузки оповещений:', error);
|
||||
}
|
||||
setLoading(false);
|
||||
};
|
||||
|
||||
const handleRespondToAlert = async (alertId) => {
|
||||
try {
|
||||
await respondToAlert(alertId, 'help_on_way', 'Еду на помощь!', 10);
|
||||
Alert.alert('Успешно', 'Ваш отклик отправлен');
|
||||
loadNearbyAlerts(); // Обновить список
|
||||
} catch (error) {
|
||||
Alert.alert('Ошибка', 'Не удалось отправить отклик');
|
||||
}
|
||||
};
|
||||
|
||||
const renderAlert = ({ item }) => (
|
||||
<View style={{
|
||||
backgroundColor: 'white',
|
||||
margin: 10,
|
||||
padding: 15,
|
||||
borderRadius: 10,
|
||||
elevation: 2
|
||||
}}>
|
||||
<Text style={{ fontWeight: 'bold', fontSize: 16 }}>
|
||||
{item.alert_type.toUpperCase()}
|
||||
</Text>
|
||||
<Text style={{ color: '#666', marginTop: 5 }}>
|
||||
📍 {item.address || 'Адрес не указан'}
|
||||
</Text>
|
||||
<Text style={{ color: '#666' }}>
|
||||
📏 {item.distance_km.toFixed(1)} км от вас
|
||||
</Text>
|
||||
<Text style={{ color: '#666' }}>
|
||||
👥 Откликов: {item.responded_users_count}
|
||||
</Text>
|
||||
|
||||
<TouchableOpacity
|
||||
style={{
|
||||
backgroundColor: '#007AFF',
|
||||
padding: 10,
|
||||
borderRadius: 5,
|
||||
marginTop: 10,
|
||||
alignItems: 'center'
|
||||
}}
|
||||
onPress={() => handleRespondToAlert(item.id)}
|
||||
>
|
||||
<Text style={{ color: 'white', fontWeight: 'bold' }}>
|
||||
Помочь
|
||||
</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={{ flex: 1 }}>
|
||||
<Text style={{ fontSize: 18, fontWeight: 'bold', margin: 15 }}>
|
||||
Ближайшие оповещения
|
||||
</Text>
|
||||
|
||||
{loading ? (
|
||||
<Text style={{ textAlign: 'center', margin: 20 }}>
|
||||
Загрузка...
|
||||
</Text>
|
||||
) : (
|
||||
<FlatList
|
||||
data={alerts}
|
||||
renderItem={renderAlert}
|
||||
keyExtractor={(item) => item.id.toString()}
|
||||
refreshing={loading}
|
||||
onRefresh={loadNearbyAlerts}
|
||||
ListEmptyComponent={
|
||||
<Text style={{ textAlign: 'center', margin: 20, color: '#666' }}>
|
||||
Поблизости нет активных оповещений
|
||||
</Text>
|
||||
}
|
||||
/>
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
```
|
||||
|
||||
## 🔄 Обновление местоположения в реальном времени
|
||||
|
||||
```javascript
|
||||
class LocationService {
|
||||
constructor() {
|
||||
this.watchId = null;
|
||||
this.lastUpdate = null;
|
||||
}
|
||||
|
||||
startLocationTracking() {
|
||||
this.watchId = Geolocation.watchPosition(
|
||||
(position) => {
|
||||
this.updateLocation(position.coords);
|
||||
},
|
||||
(error) => {
|
||||
console.error('Location error:', error);
|
||||
},
|
||||
{
|
||||
enableHighAccuracy: true,
|
||||
distanceFilter: 10, // Обновлять при изменении на 10 метров
|
||||
interval: 30000, // Обновлять каждые 30 секунд
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
async updateLocation(coords) {
|
||||
const now = Date.now();
|
||||
|
||||
// Не отправляем слишком часто
|
||||
if (this.lastUpdate && (now - this.lastUpdate) < 30000) {
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const token = localStorage.getItem('access_token');
|
||||
|
||||
await fetch('http://192.168.0.103:8000/api/location/update', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${token}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
latitude: coords.latitude,
|
||||
longitude: coords.longitude,
|
||||
accuracy: coords.accuracy,
|
||||
speed: coords.speed,
|
||||
heading: coords.heading,
|
||||
timestamp: new Date().toISOString()
|
||||
})
|
||||
});
|
||||
|
||||
this.lastUpdate = now;
|
||||
} catch (error) {
|
||||
console.error('Failed to update location:', error);
|
||||
}
|
||||
}
|
||||
|
||||
stopLocationTracking() {
|
||||
if (this.watchId) {
|
||||
Geolocation.clearWatch(this.watchId);
|
||||
this.watchId = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔔 Push уведомления
|
||||
|
||||
### Настройка Firebase (Android) / APNS (iOS)
|
||||
```javascript
|
||||
import messaging from '@react-native-firebase/messaging';
|
||||
|
||||
class PushNotificationService {
|
||||
async requestPermission() {
|
||||
const authStatus = await messaging().requestPermission();
|
||||
const enabled =
|
||||
authStatus === messaging.AuthorizationStatus.AUTHORIZED ||
|
||||
authStatus === messaging.AuthorizationStatus.PROVISIONAL;
|
||||
|
||||
if (enabled) {
|
||||
console.log('Push permissions granted');
|
||||
await this.getToken();
|
||||
}
|
||||
}
|
||||
|
||||
async getToken() {
|
||||
try {
|
||||
const token = await messaging().getToken();
|
||||
await this.registerToken(token);
|
||||
} catch (error) {
|
||||
console.error('Failed to get push token:', error);
|
||||
}
|
||||
}
|
||||
|
||||
async registerToken(token) {
|
||||
const apiToken = localStorage.getItem('access_token');
|
||||
|
||||
await fetch('http://192.168.0.103:8000/api/notifications/register-token', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
'Authorization': `Bearer ${apiToken}`
|
||||
},
|
||||
body: JSON.stringify({
|
||||
token: token,
|
||||
platform: Platform.OS, // 'ios' or 'android'
|
||||
app_version: '1.0.0'
|
||||
});
|
||||
}
|
||||
|
||||
setupForegroundHandler() {
|
||||
messaging().onMessage(async remoteMessage => {
|
||||
// Показать уведомление когда приложение активно
|
||||
Alert.alert(
|
||||
remoteMessage.notification.title,
|
||||
remoteMessage.notification.body
|
||||
);
|
||||
});
|
||||
}
|
||||
|
||||
setupBackgroundHandler() {
|
||||
messaging().setBackgroundMessageHandler(async remoteMessage => {
|
||||
console.log('Background message:', remoteMessage);
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🔐 Безопасность и шифрование
|
||||
|
||||
### Хранение токенов
|
||||
```javascript
|
||||
import * as Keychain from 'react-native-keychain';
|
||||
|
||||
class SecureStorage {
|
||||
static async storeToken(token) {
|
||||
try {
|
||||
await Keychain.setInternetCredentials(
|
||||
'women_safety_app',
|
||||
'access_token',
|
||||
token
|
||||
);
|
||||
} catch (error) {
|
||||
console.error('Failed to store token:', error);
|
||||
}
|
||||
}
|
||||
|
||||
static async getToken() {
|
||||
try {
|
||||
const credentials = await Keychain.getInternetCredentials('women_safety_app');
|
||||
if (credentials) {
|
||||
return credentials.password;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Failed to get token:', error);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
static async removeToken() {
|
||||
try {
|
||||
await Keychain.resetInternetCredentials('women_safety_app');
|
||||
} catch (error) {
|
||||
console.error('Failed to remove token:', error);
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### Шифрование местоположения
|
||||
```javascript
|
||||
import CryptoJS from 'crypto-js';
|
||||
|
||||
class LocationEncryption {
|
||||
static encryptLocation(lat, lng, secretKey) {
|
||||
const locationData = JSON.stringify({ lat, lng, timestamp: Date.now() });
|
||||
return CryptoJS.AES.encrypt(locationData, secretKey).toString();
|
||||
}
|
||||
|
||||
static decryptLocation(encryptedData, secretKey) {
|
||||
try {
|
||||
const bytes = CryptoJS.AES.decrypt(encryptedData, secretKey);
|
||||
const decryptedData = bytes.toString(CryptoJS.enc.Utf8);
|
||||
return JSON.parse(decryptedData);
|
||||
} catch (error) {
|
||||
console.error('Failed to decrypt location:', error);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 📊 Аналитика и мониторинг
|
||||
|
||||
### Отслеживание использования
|
||||
```javascript
|
||||
class AnalyticsService {
|
||||
static trackEmergencyAlert(alertType, responseTime) {
|
||||
// Интеграция с аналитикой (Firebase Analytics, etc.)
|
||||
analytics().logEvent('emergency_alert_created', {
|
||||
alert_type: alertType,
|
||||
response_time_seconds: responseTime,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
static trackUserResponse(alertId, responseType) {
|
||||
analytics().logEvent('emergency_response', {
|
||||
alert_id: alertId,
|
||||
response_type: responseType,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
static trackLocationUpdate(accuracy) {
|
||||
analytics().logEvent('location_update', {
|
||||
accuracy_meters: accuracy,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 🧪 Тестирование API
|
||||
|
||||
### Unit тесты для API клиента
|
||||
```javascript
|
||||
import { APIClient } from './APIClient';
|
||||
|
||||
describe('APIClient', () => {
|
||||
let client;
|
||||
|
||||
beforeEach(() => {
|
||||
client = new APIClient('http://192.168.0.103:8000');
|
||||
});
|
||||
|
||||
test('should login successfully', async () => {
|
||||
const result = await client.login('testuser', 'testpass');
|
||||
expect(result.access_token).toBeDefined();
|
||||
expect(result.token_type).toBe('bearer');
|
||||
});
|
||||
|
||||
test('should create emergency alert', async () => {
|
||||
// Сначала авторизоваться
|
||||
await client.login('testuser', 'testpass');
|
||||
|
||||
const alert = await client.createAlert({
|
||||
latitude: 55.7558,
|
||||
longitude: 37.6176,
|
||||
alert_type: 'general',
|
||||
message: 'Test emergency'
|
||||
});
|
||||
|
||||
expect(alert.id).toBeDefined();
|
||||
expect(alert.alert_type).toBe('general');
|
||||
expect(alert.status).toBe('active');
|
||||
});
|
||||
|
||||
test('should get nearby alerts', async () => {
|
||||
await client.login('testuser', 'testpass');
|
||||
|
||||
const alerts = await client.getNearbyAlerts(55.7558, 37.6176, 5);
|
||||
expect(Array.isArray(alerts)).toBe(true);
|
||||
});
|
||||
});
|
||||
```
|
||||
|
||||
## 📈 Оптимизация производительности
|
||||
|
||||
### Кэширование данных
|
||||
```javascript
|
||||
class CacheService {
|
||||
static cache = new Map();
|
||||
static CACHE_DURATION = 5 * 60 * 1000; // 5 минут
|
||||
|
||||
static set(key, data) {
|
||||
this.cache.set(key, {
|
||||
data,
|
||||
timestamp: Date.now()
|
||||
});
|
||||
}
|
||||
|
||||
static get(key) {
|
||||
const cached = this.cache.get(key);
|
||||
if (!cached) return null;
|
||||
|
||||
if (Date.now() - cached.timestamp > this.CACHE_DURATION) {
|
||||
this.cache.delete(key);
|
||||
return null;
|
||||
}
|
||||
|
||||
return cached.data;
|
||||
}
|
||||
|
||||
static async getNearbyAlertsWithCache(lat, lng, radius) {
|
||||
const cacheKey = `nearby_${lat}_${lng}_${radius}`;
|
||||
let alerts = this.get(cacheKey);
|
||||
|
||||
if (!alerts) {
|
||||
alerts = await findNearbyAlerts({ latitude: lat, longitude: lng }, radius);
|
||||
this.set(cacheKey, alerts);
|
||||
}
|
||||
|
||||
return alerts;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
Эти примеры покрывают все основные сценарии использования API в реальном мобильном приложении, включая безопасность, производительность и тестирование.
|
||||
Reference in New Issue
Block a user