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

This commit is contained in:
2025-09-26 12:22:14 +09:00
parent ca32dc8867
commit 7c22664daf
33 changed files with 3267 additions and 1429 deletions

1
-d
View File

@@ -1 +0,0 @@
{"detail":[{"type":"missing","loc":["body"],"msg":"Field required","input":null,"url":"https://errors.pydantic.dev/2.4/v/missing"}]}

View File

@@ -1,684 +0,0 @@
# Запросы для тестирования API приложения для безопасности женщин
В этом файле собраны примеры всех API-запросов для тестирования серверной части микросервисного приложения.
## Содержание
- [Общие запросы](#общие-запросы)
- [Сервис пользователей (User Service)](#сервис-пользователей-user-service)
- [Сервис экстренной помощи (Emergency Service)](#сервис-экстренной-помощи-emergency-service)
- [Сервис геолокации (Location Service)](#сервис-геолокации-location-service)
- [Сервис календаря (Calendar Service)](#сервис-календаря-calendar-service)
- [Сервис уведомлений (Notification Service)](#сервис-уведомлений-notification-service)
- [API Gateway](#api-gateway)
## Общие запросы
### Проверка доступности всех сервисов
```bash
# Проверка API Gateway
curl -s http://localhost:8000/health | jq
# Проверка User Service
curl -s http://localhost:8001/health | jq
# Проверка Emergency Service
curl -s http://localhost:8002/health | jq
# Проверка Location Service
curl -s http://localhost:8003/health | jq
# Проверка Calendar Service
curl -s http://localhost:8004/health | jq
# Проверка Notification Service
curl -s http://localhost:8005/health | jq
# Альтернативные endpoint health
curl -s http://localhost:8000/api/v1/health | jq
curl -s http://localhost:8001/api/v1/health | jq
curl -s http://localhost:8002/api/v1/health | jq
curl -s http://localhost:8003/api/v1/health | jq
curl -s http://localhost:8004/api/v1/health | jq
curl -s http://localhost:8005/api/v1/health | jq
```
## Сервис пользователей (User Service)
### Регистрация нового пользователя
```bash
curl -X POST http://localhost:8001/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{
"username": "usertest1",
"email": "user1@example.com",
"password": "SecurePass123!",
"full_name": "Тест Пользователь",
"phone_number": "+79001234567"
}' | jq
```
### Авторизация пользователя
```bash
curl -X POST http://localhost:8001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "usertest1",
"password": "SecurePass123!"
}' | jq
# Сохраните полученный токен в переменную для дальнейшего использования
# TOKEN=$(curl -s -X POST http://localhost:8001/api/v1/auth/login -H "Content-Type: application/json" -d '{"username": "usertest1", "password": "SecurePass123!"}' | jq -r '.access_token')
```
### Получение информации о текущем пользователе
```bash
curl -X GET http://localhost:8001/api/v1/users/me \
-H "Authorization: Bearer $TOKEN" | jq
```
### Обновление профиля пользователя
```bash
curl -X PATCH http://localhost:8001/api/v1/users/me \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"full_name": "Новое Имя Пользователя",
"phone_number": "+79009876543"
}' | jq
```
### Добавление контактов для экстренной связи
```bash
curl -X POST http://localhost:8001/api/v1/users/me/emergency-contacts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Контакт для экстренной связи",
"phone_number": "+79001112233",
"relationship": "Друг"
}' | jq
```
### Получение списка контактов для экстренной связи
```bash
curl -X GET http://localhost:8001/api/v1/users/me/emergency-contacts \
-H "Authorization: Bearer $TOKEN" | jq
```
### Удаление контакта для экстренной связи
```bash
# Замените {contact_id} на реальный ID контакта
curl -X DELETE http://localhost:8001/api/v1/users/me/emergency-contacts/{contact_id} \
-H "Authorization: Bearer $TOKEN"
```
### Изменение пароля
```bash
curl -X POST http://localhost:8001/api/v1/users/me/change-password \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"current_password": "SecurePass123!",
"new_password": "NewSecurePass456!"
}' | jq
```
## Сервис экстренной помощи (Emergency Service)
### Отправка сигнала SOS
```bash
curl -X POST http://localhost:8002/api/v1/emergency/alerts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"alert_type": "SOS",
"message": "Нужна срочная помощь!"
}' | jq
# Сохраните ID созданного оповещения для дальнейших действий
# ALERT_ID=$(curl -s -X POST http://localhost:8002/api/v1/emergency/alerts -H "Content-Type: application/json" -H "Authorization: Bearer $TOKEN" -d '{"latitude": 55.7558, "longitude": 37.6173, "alert_type": "SOS", "message": "Нужна срочная помощь!"}' | jq -r '.id')
```
### Получение активных оповещений пользователя
```bash
curl -X GET http://localhost:8002/api/v1/emergency/alerts/my \
-H "Authorization: Bearer $TOKEN" | jq
```
### Отмена сигнала SOS
```bash
# Замените {alert_id} на ID полученного ранее оповещения
curl -X PATCH http://localhost:8002/api/v1/emergency/alerts/{alert_id}/cancel \
-H "Authorization: Bearer $TOKEN" | jq
```
### Получение списка ближайших оповещений (для волонтеров/служб)
```bash
curl -X GET "http://localhost:8002/api/v1/emergency/alerts/nearby?latitude=55.7558&longitude=37.6173&radius=1.0" \
-H "Authorization: Bearer $TOKEN" | jq
```
### Отправка отчета о происшествии
```bash
curl -X POST http://localhost:8002/api/v1/emergency/reports \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"report_type": "UNSAFE_AREA",
"description": "Тёмная улица без освещения",
"severity_level": "MEDIUM"
}' | jq
```
### Получение списка отчетов о происшествиях в определенном радиусе
```bash
curl -X GET "http://localhost:8002/api/v1/emergency/reports/nearby?latitude=55.7558&longitude=37.6173&radius=5.0" \
-H "Authorization: Bearer $TOKEN" | jq
```
## Сервис геолокации (Location Service)
### Обновление местоположения пользователя
```bash
curl -X POST http://localhost:8003/api/v1/locations/update \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"accuracy": 10.0
}' | jq
```
### Получение последнего известного местоположения пользователя
```bash
curl -X GET http://localhost:8003/api/v1/locations/last \
-H "Authorization: Bearer $TOKEN" | jq
```
### Получение истории перемещений пользователя
```bash
curl -X GET "http://localhost:8003/api/v1/locations/history?start_date=2025-09-20T00:00:00Z&end_date=2025-09-25T23:59:59Z" \
-H "Authorization: Bearer $TOKEN" | jq
```
### Получение списка безопасных мест поблизости
```bash
curl -X GET "http://localhost:8003/api/v1/locations/safe-places?latitude=55.7558&longitude=37.6173&radius=1.0" \
-H "Authorization: Bearer $TOKEN" | jq
```
### Добавление безопасного места
```bash
curl -X POST http://localhost:8003/api/v1/locations/safe-places \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Полицейский участок",
"latitude": 55.7559,
"longitude": 37.6174,
"place_type": "POLICE",
"description": "Центральный полицейский участок"
}' | jq
```
### Поиск пользователей в определенном радиусе (для экстренных ситуаций)
```bash
curl -X GET "http://localhost:8003/api/v1/locations/users/nearby?latitude=55.7558&longitude=37.6173&radius=0.5" \
-H "Authorization: Bearer $TOKEN" | jq
```
## Сервис календаря (Calendar Service)
### Создание записи в календаре
```bash
curl -X POST http://localhost:8004/api/v1/calendar/entries \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"entry_date": "2025-10-01",
"cycle_day": 1,
"symptoms": ["HEADACHE", "FATIGUE"],
"mood": "NORMAL",
"notes": "Начало цикла"
}' | jq
```
### Получение записей календаря за период
```bash
curl -X GET "http://localhost:8004/api/v1/calendar/entries?start_date=2025-09-01&end_date=2025-10-31" \
-H "Authorization: Bearer $TOKEN" | jq
```
### Обновление записи в календаре
```bash
# Замените {entry_id} на реальный ID записи
curl -X PATCH http://localhost:8004/api/v1/calendar/entries/{entry_id} \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"symptoms": ["HEADACHE", "FATIGUE", "BLOATING"],
"mood": "IRRITABLE",
"notes": "Обновленная запись"
}' | jq
```
### Получение статистики и прогнозов
```bash
curl -X GET http://localhost:8004/api/v1/calendar/statistics \
-H "Authorization: Bearer $TOKEN" | jq
```
### Получение прогноза следующего цикла
```bash
curl -X GET http://localhost:8004/api/v1/calendar/predictions/next-cycle \
-H "Authorization: Bearer $TOKEN" | jq
```
### Настройка параметров календаря
```bash
curl -X POST http://localhost:8004/api/v1/calendar/settings \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"average_cycle_length": 28,
"average_period_length": 5,
"notification_enabled": true,
"notification_days_before": 2
}' | jq
```
### Получение настроек календаря
```bash
curl -X GET http://localhost:8004/api/v1/calendar/settings \
-H "Authorization: Bearer $TOKEN" | jq
```
## Сервис уведомлений (Notification Service)
### Регистрация устройства для Push-уведомлений
```bash
curl -X POST http://localhost:8005/api/v1/notifications/devices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"device_token": "fcm-token-example-123456",
"device_type": "ANDROID",
"app_version": "1.0.0"
}' | jq
```
### Получение списка зарегистрированных устройств
```bash
curl -X GET http://localhost:8005/api/v1/notifications/devices \
-H "Authorization: Bearer $TOKEN" | jq
```
### Удаление регистрации устройства
```bash
# Замените {device_id} на реальный ID устройства
curl -X DELETE http://localhost:8005/api/v1/notifications/devices/{device_id} \
-H "Authorization: Bearer $TOKEN"
```
### Настройка предпочтений уведомлений
```bash
curl -X POST http://localhost:8005/api/v1/notifications/preferences \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"emergency_alerts": true,
"nearby_incidents": true,
"calendar_reminders": true,
"system_notifications": true
}' | jq
```
### Получение настроек уведомлений
```bash
curl -X GET http://localhost:8005/api/v1/notifications/preferences \
-H "Authorization: Bearer $TOKEN" | jq
```
### Отправка тестового уведомления (только для тестирования)
```bash
curl -X POST http://localhost:8005/api/v1/notifications/test \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"title": "Тестовое уведомление",
"body": "Это тестовое push-уведомление",
"data": {
"type": "TEST",
"action": "OPEN_APP"
}
}' | jq
```
### Получение истории уведомлений
```bash
curl -X GET http://localhost:8005/api/v1/notifications/history \
-H "Authorization: Bearer $TOKEN" | jq
```
## API Gateway
### Проверка статуса всех сервисов через Gateway
```bash
curl -X GET http://localhost:8000/api/v1/status \
-H "Authorization: Bearer $TOKEN" | jq
```
### Регистрация через Gateway
```bash
curl -X POST http://localhost:8000/api/v1/auth/register \
-H "Content-Type: application/json" \
-d '{
"username": "gatewayuser",
"email": "gateway@example.com",
"password": "GatewayPass123!",
"full_name": "Gateway Test User",
"phone_number": "+79991234567"
}' | jq
```
### Авторизация через Gateway
```bash
curl -X POST http://localhost:8000/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "gatewayuser",
"password": "GatewayPass123!"
}' | jq
# Сохраните полученный токен в переменную для дальнейшего использования
# GW_TOKEN=$(curl -s -X POST http://localhost:8000/api/v1/auth/login -H "Content-Type: application/json" -d '{"username": "gatewayuser", "password": "GatewayPass123!"}' | jq -r '.access_token')
```
### Создание экстренного оповещения через Gateway
```bash
curl -X POST http://localhost:8000/api/v1/emergency/alerts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $GW_TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"alert_type": "SOS",
"message": "Тестовое экстренное оповещение через Gateway"
}' | jq
```
### Комплексный запрос данных пользователя через Gateway
```bash
curl -X GET http://localhost:8000/api/v1/users/dashboard \
-H "Authorization: Bearer $GW_TOKEN" | jq
```
## Bash скрипт для полного тестирования API
Создайте файл `full_api_test.sh` со следующим содержимым:
```bash
#!/bin/bash
# Colors for output
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color
echo -e "${YELLOW}=======================================${NC}"
echo -e "${YELLOW}🔍 Полное тестирование API приложения ${NC}"
echo -e "${YELLOW}=======================================${NC}"
# 1. Проверка доступности сервисов
echo -e "\n${GREEN}1. Проверка доступности сервисов${NC}"
services=(
"http://localhost:8000"
"http://localhost:8001"
"http://localhost:8002"
"http://localhost:8003"
"http://localhost:8004"
"http://localhost:8005"
)
service_names=(
"API Gateway"
"User Service"
"Emergency Service"
"Location Service"
"Calendar Service"
"Notification Service"
)
for i in ${!services[@]}; do
echo -n "Проверка ${service_names[$i]} (${services[$i]})... "
if curl -s "${services[$i]}/health" | grep -q "status.*healthy" || curl -s "${services[$i]}/api/v1/health" | grep -q "status.*healthy"; then
echo -e "${GREEN}OK${NC}"
else
echo -e "${RED}НЕДОСТУПНО${NC}"
echo "Продолжение тестирования может вызвать ошибки. Хотите продолжить? (y/n)"
read continue_test
if [[ $continue_test != "y" ]]; then
exit 1
fi
fi
done
# 2. Регистрация и авторизация
echo -e "\n${GREEN}2. Регистрация и авторизация${NC}"
echo "Регистрация тестового пользователя..."
REGISTER_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/users/register \
-H "Content-Type: application/json" \
-d '{
"username": "apitest",
"email": "apitest@example.com",
"password": "ApiTest123!",
"full_name": "API Test User",
"phone_number": "+79997776655"
}')
echo $REGISTER_RESPONSE | jq
# Авторизация
echo "Авторизация тестового пользователя..."
LOGIN_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/auth/login \
-H "Content-Type: application/json" \
-d '{
"username": "apitest",
"password": "ApiTest123!"
}')
echo $LOGIN_RESPONSE | jq
TOKEN=$(echo $LOGIN_RESPONSE | jq -r '.access_token')
if [[ $TOKEN == "null" || -z $TOKEN ]]; then
echo -e "${RED}Не удалось получить токен авторизации. Тестирование будет остановлено.${NC}"
exit 1
else
echo -e "${GREEN}Успешно получен токен авторизации${NC}"
fi
# 3. Обновление профиля
echo -e "\n${GREEN}3. Обновление профиля пользователя${NC}"
curl -s -X PATCH http://localhost:8001/api/v1/users/me \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"full_name": "Обновленное Имя",
"phone_number": "+79997776655"
}' | jq
# 4. Добавление контактов экстренной связи
echo -e "\n${GREEN}4. Добавление контакта для экстренной связи${NC}"
CONTACT_RESPONSE=$(curl -s -X POST http://localhost:8001/api/v1/users/me/emergency-contacts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"name": "Экстренный контакт",
"phone_number": "+79991112233",
"relationship": "Родственник"
}')
echo $CONTACT_RESPONSE | jq
CONTACT_ID=$(echo $CONTACT_RESPONSE | jq -r '.id')
# 5. Получение списка контактов
echo -e "\n${GREEN}5. Получение списка экстренных контактов${NC}"
curl -s -X GET http://localhost:8001/api/v1/users/me/emergency-contacts \
-H "Authorization: Bearer $TOKEN" | jq
# 6. Обновление местоположения пользователя
echo -e "\n${GREEN}6. Обновление местоположения пользователя${NC}"
curl -s -X POST http://localhost:8003/api/v1/locations/update \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"accuracy": 10.0
}' | jq
# 7. Получение последнего местоположения
echo -e "\n${GREEN}7. Получение последнего местоположения${NC}"
curl -s -X GET http://localhost:8003/api/v1/locations/last \
-H "Authorization: Bearer $TOKEN" | jq
# 8. Создание экстренного оповещения
echo -e "\n${GREEN}8. Создание экстренного оповещения${NC}"
ALERT_RESPONSE=$(curl -s -X POST http://localhost:8002/api/v1/emergency/alerts \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6173,
"alert_type": "SOS",
"message": "Тестовое экстренное оповещение"
}')
echo $ALERT_RESPONSE | jq
ALERT_ID=$(echo $ALERT_RESPONSE | jq -r '.id')
# 9. Получение активных оповещений
echo -e "\n${GREEN}9. Получение активных оповещений пользователя${NC}"
curl -s -X GET http://localhost:8002/api/v1/emergency/alerts/my \
-H "Authorization: Bearer $TOKEN" | jq
# 10. Отмена экстренного оповещения
if [[ $ALERT_ID != "null" && -n $ALERT_ID ]]; then
echo -e "\n${GREEN}10. Отмена экстренного оповещения${NC}"
curl -s -X PATCH http://localhost:8002/api/v1/emergency/alerts/$ALERT_ID/cancel \
-H "Authorization: Bearer $TOKEN" | jq
fi
# 11. Создание записи в календаре
echo -e "\n${GREEN}11. Создание записи в календаре${NC}"
CALENDAR_RESPONSE=$(curl -s -X POST http://localhost:8004/api/v1/calendar/entries \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"entry_date": "2025-10-01",
"cycle_day": 1,
"symptoms": ["HEADACHE", "FATIGUE"],
"mood": "NORMAL",
"notes": "Тестовая запись в календаре"
}')
echo $CALENDAR_RESPONSE | jq
# 12. Получение записей календаря
echo -e "\n${GREEN}12. Получение записей календаря${NC}"
curl -s -X GET "http://localhost:8004/api/v1/calendar/entries?start_date=2025-09-01&end_date=2025-10-31" \
-H "Authorization: Bearer $TOKEN" | jq
# 13. Регистрация устройства для уведомлений
echo -e "\n${GREEN}13. Регистрация устройства для уведомлений${NC}"
DEVICE_RESPONSE=$(curl -s -X POST http://localhost:8005/api/v1/notifications/devices \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"device_token": "fcm-test-token-123",
"device_type": "ANDROID",
"app_version": "1.0.0"
}')
echo $DEVICE_RESPONSE | jq
# 14. Настройка предпочтений уведомлений
echo -e "\n${GREEN}14. Настройка предпочтений уведомлений${NC}"
curl -s -X POST http://localhost:8005/api/v1/notifications/preferences \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"emergency_alerts": true,
"nearby_incidents": true,
"calendar_reminders": true,
"system_notifications": true
}' | jq
# 15. Получение данных пользователя через Gateway
echo -e "\n${GREEN}15. Получение данных пользователя через Gateway${NC}"
curl -s -X GET http://localhost:8000/api/v1/users/dashboard \
-H "Authorization: Bearer $TOKEN" | jq
echo -e "\n${YELLOW}=======================================${NC}"
echo -e "${GREEN}✅ Тестирование API завершено успешно${NC}"
echo -e "${YELLOW}=======================================${NC}"
```
## Использование Bash скрипта для полного тестирования API
Для запуска скрипта выполните следующие команды:
```bash
chmod +x full_api_test.sh
./full_api_test.sh
```

View File

@@ -1,40 +0,0 @@
# Результаты тестирования API
Дата: 2025-09-25 14:57:40
## Итоги
- Всего тестов: 32
- Успешно: 13
- Ошибок: 19
## Подробности
✓ УСПЕШНО: Сервис API Gateway доступен
✓ УСПЕШНО: Сервис User Service доступен
✓ УСПЕШНО: Сервис Emergency Service доступен
✓ УСПЕШНО: Сервис Location Service доступен
✓ УСПЕШНО: Сервис Calendar Service доступен
✓ УСПЕШНО: Сервис Notification Service доступен
✗ ОШИБКА: Регистрация пользователя
✗ ОШИБКА: Проверка на дублирование пользователя (должна быть ошибка)
✓ УСПЕШНО: Получение токена авторизации
✗ ОШИБКА: Проверка авторизации с неверным паролем (должна быть ошибка)
✓ УСПЕШНО: Получение профиля пользователя
✓ УСПЕШНО: Получение профиля через альтернативный эндпоинт
✗ ОШИБКА: Обновление профиля пользователя
✗ ОШИБКА: Проверка обновления имени в профиле
✗ ОШИБКА: Добавление экстренного контакта
✓ УСПЕШНО: Получение списка экстренных контактов (найдено: 1)
✗ ОШИБКА: Обновление местоположения
✗ ОШИБКА: Получение последнего местоположения
✓ УСПЕШНО: Получение истории местоположений
✗ ОШИБКА: Создание экстренного оповещения
✓ УСПЕШНО: Получение активных оповещений (найдено: 1)
✗ ОШИБКА: Получение статистики по оповещениям
✗ ОШИБКА: Создание записи в календаре
✓ УСПЕШНО: Получение записей календаря (найдено: 1)
✗ ОШИБКА: Получение прогноза цикла
✗ ОШИБКА: Регистрация устройства для уведомлений
✗ ОШИБКА: Настройка предпочтений уведомлений
✗ ОШИБКА: Получение предпочтений уведомлений
✗ ОШИБКА: Отправка тестового уведомления
✗ ОШИБКА: Получение данных пользователя через API Gateway
✗ ОШИБКА: Получение OpenAPI схемы
✗ ОШИБКА: Проверка наличия схем данных

617
docs/DATA_SCHEMAS.md Normal file
View 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.

View 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
View 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
```
Скрипт проверит:
- Регистрацию и авторизацию
- Создание и управление оповещениями
- Отклики на оповещения
- Отчеты и отметки безопасности
- Статистику и аналитику

View 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 в реальном мобильном приложении, включая безопасность, производительность и тестирование.

1
login.json Normal file
View File

@@ -0,0 +1 @@
{"access_token":"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOCIsImVtYWlsIjoiYXBpdGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTc1ODg1NzA3Nn0.HO3jIZIzAFz0ojGVmExnnrwu5bmw42nMVnnCDiu9xHI","token_type":"bearer"}

View File

@@ -1,25 +1,56 @@
import asyncio
from datetime import datetime, timedelta
from typing import List
from typing import List, Optional
import math
import httpx
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, status
from fastapi import BackgroundTasks, Depends, FastAPI, HTTPException, Query, status
from fastapi.middleware.cors import CORSMiddleware
from sqlalchemy import func, select, update
from sqlalchemy import func, select, update, desc, and_, or_
from sqlalchemy.ext.asyncio import AsyncSession
from services.emergency_service.models import EmergencyAlert, EmergencyResponse
from services.emergency_service.models import EmergencyAlert, EmergencyResponse, EmergencyReport, SafetyCheck
from services.emergency_service.schemas import (
AlertStatus,
AlertType,
EmergencyAlertCreate,
EmergencyAlertResponse,
EmergencyAlertUpdate,
EmergencyResponseCreate,
EmergencyResponseResponse,
EmergencyStats,
EmergencyStatistics,
EmergencyReportCreate,
EmergencyReportResponse,
NearbyAlertResponse,
SafetyCheckCreate,
SafetyCheckResponse,
)
from services.user_service.models import User
# Упрощенная модель User для Emergency Service
from sqlalchemy import Column, Integer, String, Boolean
from shared.database import BaseModel
class User(BaseModel):
__tablename__ = "users"
id = Column(Integer, primary_key=True, index=True)
username = Column(String, unique=True, index=True)
email = Column(String, unique=True, index=True)
is_active = Column(Boolean, default=True)
from shared.auth import get_current_user_from_token
from shared.config import settings
from shared.database import get_db
from shared.database import AsyncSessionLocal
# Database dependency
async def get_db():
async with AsyncSessionLocal() as session:
try:
yield session
except Exception:
await session.rollback()
raise
finally:
await session.close()
app = FastAPI(title="Emergency Service", version="1.0.0")
@@ -48,6 +79,21 @@ async def get_current_user(
return user
def calculate_distance(lat1: float, lon1: float, lat2: float, lon2: float) -> float:
"""Calculate distance between two points in kilometers using Haversine formula."""
# Convert latitude and longitude from degrees to radians
lat1, lon1, lat2, lon2 = map(math.radians, [lat1, lon1, lat2, lon2])
# Haversine formula
dlat = lat2 - lat1
dlon = lon2 - lon1
a = math.sin(dlat/2)**2 + math.cos(lat1) * math.cos(lat2) * math.sin(dlon/2)**2
c = 2 * math.asin(math.sqrt(a))
# Radius of earth in kilometers
r = 6371
return c * r
@app.get("/health")
async def health_check():
"""Health check endpoint"""
@@ -176,7 +222,7 @@ async def respond_to_alert(
existing_response = await db.execute(
select(EmergencyResponse).filter(
EmergencyResponse.alert_id == alert_id,
EmergencyResponse.user_id == current_user.id
EmergencyResponse.responder_id == current_user.id
)
)
if existing_response.scalars().first():
@@ -188,7 +234,7 @@ async def respond_to_alert(
# Create response
db_response = EmergencyResponse(
alert_id=alert_id,
user_id=current_user.id,
responder_id=current_user.id,
response_type=response_data.response_type,
message=response_data.message,
eta_minutes=response_data.eta_minutes,
@@ -206,7 +252,12 @@ async def respond_to_alert(
await db.commit()
await db.refresh(db_response)
return EmergencyResponseResponse.model_validate(db_response)
# Create response with responder info
response_dict = db_response.__dict__.copy()
response_dict['responder_name'] = current_user.username
response_dict['responder_phone'] = getattr(current_user, 'phone_number', None)
return EmergencyResponseResponse.model_validate(response_dict)
@app.put("/api/v1/alert/{alert_id}/resolve")
@@ -297,7 +348,7 @@ async def get_emergency_reports(
return [EmergencyAlertResponse.model_validate(alert) for alert in alerts]
@app.get("/api/v1/stats", response_model=EmergencyStats)
@app.get("/api/v1/stats", response_model=EmergencyStatistics)
async def get_emergency_stats(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
@@ -320,15 +371,203 @@ async def get_emergency_stats(
total_responders_result = await db.execute(select(func.count(EmergencyResponse.id)))
total_responders = total_responders_result.scalar() or 0
return EmergencyStats(
return EmergencyStatistics(
total_alerts=total_alerts,
active_alerts=active_alerts,
resolved_alerts=resolved_alerts,
avg_response_time_minutes=None, # TODO: Calculate this
avg_response_time_minutes=0, # TODO: Calculate this
total_responders=total_responders,
)
@app.get("/api/v1/alerts/nearby", response_model=List[NearbyAlertResponse])
async def get_nearby_alerts(
latitude: float = Query(..., ge=-90, le=90),
longitude: float = Query(..., ge=-180, le=180),
radius_km: float = Query(default=10.0, ge=0.1, le=100),
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get nearby emergency alerts within specified radius"""
# For now, return all active alerts (in production, add distance filtering)
result = await db.execute(
select(EmergencyAlert)
.filter(EmergencyAlert.is_resolved == False)
.order_by(EmergencyAlert.created_at.desc())
.limit(20)
)
alerts = result.scalars().all()
nearby_alerts = []
for alert in alerts:
distance = calculate_distance(latitude, longitude, alert.latitude, alert.longitude)
if distance <= radius_km:
nearby_alerts.append(NearbyAlertResponse(
id=alert.id,
alert_type=alert.alert_type,
latitude=alert.latitude,
longitude=alert.longitude,
address=alert.address,
distance_km=round(distance, 2),
created_at=alert.created_at,
responded_users_count=alert.responded_users_count or 0
))
return sorted(nearby_alerts, key=lambda x: x.distance_km)
@app.post("/api/v1/report", response_model=EmergencyReportResponse)
async def create_emergency_report(
report_data: EmergencyReportCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Create emergency report"""
db_report = EmergencyReport(
user_id=current_user.id if not report_data.is_anonymous else None,
latitude=report_data.latitude,
longitude=report_data.longitude,
address=report_data.address,
report_type=report_data.report_type,
description=report_data.description,
is_anonymous=report_data.is_anonymous,
severity=report_data.severity
)
db.add(db_report)
await db.commit()
await db.refresh(db_report)
return EmergencyReportResponse.model_validate(db_report)
@app.get("/api/v1/reports", response_model=List[EmergencyReportResponse])
async def get_emergency_reports(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get emergency reports"""
result = await db.execute(
select(EmergencyReport)
.order_by(EmergencyReport.created_at.desc())
.limit(50)
)
reports = result.scalars().all()
return [EmergencyReportResponse.model_validate(report) for report in reports]
@app.post("/api/v1/safety-check", response_model=SafetyCheckResponse)
async def create_safety_check(
check_data: SafetyCheckCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Create safety check-in"""
db_check = SafetyCheck(
user_id=current_user.id,
message=check_data.message,
location_latitude=check_data.location_latitude,
location_longitude=check_data.location_longitude
)
db.add(db_check)
await db.commit()
await db.refresh(db_check)
return SafetyCheckResponse.model_validate(db_check)
@app.get("/api/v1/safety-checks", response_model=List[SafetyCheckResponse])
async def get_safety_checks(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get user's safety check-ins"""
result = await db.execute(
select(SafetyCheck)
.filter(SafetyCheck.user_id == current_user.id)
.order_by(SafetyCheck.created_at.desc())
.limit(50)
)
checks = result.scalars().all()
return [SafetyCheckResponse.model_validate(check) for check in checks]
@app.put("/api/v1/alert/{alert_id}", response_model=EmergencyAlertResponse)
async def update_emergency_alert(
alert_id: int,
update_data: EmergencyAlertUpdate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Update emergency alert"""
result = await db.execute(select(EmergencyAlert).filter(EmergencyAlert.id == alert_id))
alert = result.scalars().first()
if not alert:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Alert not found"
)
# Only alert creator can update
if alert.user_id != current_user.id:
raise HTTPException(
status_code=status.HTTP_403_FORBIDDEN,
detail="Only alert creator can update the alert"
)
# Update fields
update_dict = {}
if update_data.is_resolved is not None:
update_dict['is_resolved'] = update_data.is_resolved
if update_data.is_resolved:
update_dict['resolved_at'] = datetime.utcnow()
update_dict['resolved_by'] = current_user.id
if update_data.message is not None:
update_dict['message'] = update_data.message
if update_dict:
await db.execute(
update(EmergencyAlert)
.where(EmergencyAlert.id == alert_id)
.values(**update_dict)
)
await db.commit()
await db.refresh(alert)
return EmergencyAlertResponse.model_validate(alert)
@app.get("/api/v1/alert/{alert_id}/responses", response_model=List[EmergencyResponseResponse])
async def get_alert_responses(
alert_id: int,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db)
):
"""Get responses for specific alert"""
# Check if alert exists
result = await db.execute(select(EmergencyAlert).filter(EmergencyAlert.id == alert_id))
alert = result.scalars().first()
if not alert:
raise HTTPException(
status_code=status.HTTP_404_NOT_FOUND, detail="Alert not found"
)
# Get responses
responses_result = await db.execute(
select(EmergencyResponse)
.filter(EmergencyResponse.alert_id == alert_id)
.order_by(EmergencyResponse.created_at.desc())
)
responses = responses_result.scalars().all()
return [EmergencyResponseResponse.model_validate(response) for response in responses]
if __name__ == "__main__":
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8002)

View File

@@ -27,9 +27,7 @@ class EmergencyAlert(BaseModel):
address = Column(String(500))
# Alert details
alert_type = Column(
String(50), default="general"
) # general, medical, violence, etc.
alert_type = Column(String(50), default="general") # general, medical, violence, etc.
message = Column(Text)
is_resolved = Column(Boolean, default=False)
resolved_at = Column(DateTime(timezone=True))
@@ -46,9 +44,7 @@ class EmergencyAlert(BaseModel):
class EmergencyResponse(BaseModel):
__tablename__ = "emergency_responses"
alert_id = Column(
Integer, ForeignKey("emergency_alerts.id"), nullable=False, index=True
)
alert_id = Column(Integer, ForeignKey("emergency_alerts.id"), nullable=False, index=True)
responder_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
response_type = Column(String(50)) # help_on_way, contacted_authorities, etc.
@@ -56,4 +52,41 @@ class EmergencyResponse(BaseModel):
eta_minutes = Column(Integer) # Estimated time of arrival
def __repr__(self):
return f"<EmergencyResponse {self.id}>"
return f"<EmergencyResponse {self.uuid}>"
# New models for additional features
class EmergencyReport(BaseModel):
__tablename__ = "emergency_reports"
uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=True, index=True) # Nullable for anonymous reports
# Location
latitude = Column(Float, nullable=False)
longitude = Column(Float, nullable=False)
address = Column(String(500))
# Report details
report_type = Column(String(50), nullable=False)
description = Column(Text, nullable=False)
is_anonymous = Column(Boolean, default=False)
severity = Column(Integer, default=3) # 1-5 scale
status = Column(String(20), default="pending") # pending, investigating, resolved
def __repr__(self):
return f"<EmergencyReport {self.uuid}>"
class SafetyCheck(BaseModel):
__tablename__ = "safety_checks"
uuid = Column(UUID(as_uuid=True), default=uuid.uuid4, unique=True, index=True)
user_id = Column(Integer, ForeignKey("users.id"), nullable=False, index=True)
message = Column(String(200))
location_latitude = Column(Float)
location_longitude = Column(Float)
def __repr__(self):
return f"<SafetyCheck {self.uuid}>"

View File

@@ -1,6 +1,7 @@
from datetime import datetime
from enum import Enum
from typing import List, Optional
from uuid import UUID
from pydantic import BaseModel, Field
@@ -11,6 +12,16 @@ class AlertType(str, Enum):
VIOLENCE = "violence"
HARASSMENT = "harassment"
UNSAFE_AREA = "unsafe_area"
ACCIDENT = "accident"
FIRE = "fire"
NATURAL_DISASTER = "natural_disaster"
class AlertStatus(str, Enum):
ACTIVE = "active"
RESOLVED = "resolved"
CANCELLED = "cancelled"
INVESTIGATING = "investigating"
class ResponseType(str, Enum):
@@ -18,30 +29,45 @@ class ResponseType(str, Enum):
CONTACTED_AUTHORITIES = "contacted_authorities"
SAFE_NOW = "safe_now"
FALSE_ALARM = "false_alarm"
INVESTIGATING = "investigating"
RESOLVED = "resolved"
class EmergencyAlertCreate(BaseModel):
latitude: float = Field(..., ge=-90, le=90)
longitude: float = Field(..., ge=-180, le=180)
latitude: float = Field(..., ge=-90, le=90, description="Latitude coordinate")
longitude: float = Field(..., ge=-180, le=180, description="Longitude coordinate")
alert_type: AlertType = AlertType.GENERAL
message: Optional[str] = Field(None, max_length=500)
address: Optional[str] = Field(None, max_length=500)
message: Optional[str] = Field(None, max_length=500, description="Emergency description")
address: Optional[str] = Field(None, max_length=500, description="Location address")
contact_emergency_services: bool = Field(default=True, description="Contact emergency services automatically")
notify_emergency_contacts: bool = Field(default=True, description="Notify user's emergency contacts")
class EmergencyAlertUpdate(BaseModel):
message: Optional[str] = None
is_resolved: Optional[bool] = None
class EmergencyAlertResponse(BaseModel):
id: int
uuid: str
uuid: UUID
user_id: int
latitude: float
longitude: float
address: Optional[str]
alert_type: str
message: Optional[str]
is_resolved: bool
resolved_at: Optional[datetime]
notified_users_count: int
responded_users_count: int
address: Optional[str] = None
alert_type: AlertType
message: Optional[str] = None
is_resolved: bool = False
resolved_at: Optional[datetime] = None
resolved_notes: Optional[str] = None
notified_users_count: int = 0
responded_users_count: int = 0
created_at: datetime
updated_at: Optional[datetime] = None
# User information
user_name: Optional[str] = None
user_phone: Optional[str] = None
class Config:
from_attributes = True
@@ -49,33 +75,92 @@ class EmergencyAlertResponse(BaseModel):
class EmergencyResponseCreate(BaseModel):
response_type: ResponseType
message: Optional[str] = Field(None, max_length=500)
eta_minutes: Optional[int] = Field(None, ge=0, le=240) # Max 4 hours
message: Optional[str] = Field(None, max_length=500, description="Response message")
eta_minutes: Optional[int] = Field(None, ge=0, le=240, description="Estimated time of arrival in minutes")
class EmergencyResponseResponse(BaseModel):
id: int
alert_id: int
responder_id: int
response_type: str
message: Optional[str]
eta_minutes: Optional[int]
response_type: ResponseType
message: Optional[str] = None
eta_minutes: Optional[int] = None
created_at: datetime
# Responder information
responder_name: Optional[str] = None
responder_phone: Optional[str] = None
class Config:
from_attributes = True
# Report schemas
class EmergencyReportCreate(BaseModel):
latitude: float = Field(..., ge=-90, le=90)
longitude: float = Field(..., ge=-180, le=180)
report_type: str = Field(..., description="Type of emergency report")
description: str = Field(..., min_length=10, max_length=1000)
address: Optional[str] = Field(None, max_length=500)
is_anonymous: bool = Field(default=False)
severity: int = Field(default=3, ge=1, le=5, description="Severity level 1-5")
class EmergencyReportResponse(BaseModel):
id: int
uuid: UUID
user_id: Optional[int] = None
latitude: float
longitude: float
address: Optional[str] = None
report_type: str
description: str
is_anonymous: bool
severity: int
status: str = "pending"
created_at: datetime
class Config:
from_attributes = True
class NearbyUsersResponse(BaseModel):
user_id: int
distance_meters: float
# Statistics schemas
class EmergencyStatistics(BaseModel):
total_alerts: int = 0
active_alerts: int = 0
resolved_alerts: int = 0
total_responders: int = 0
avg_response_time_minutes: float = 0
# Location-based schemas
class NearbyAlertResponse(BaseModel):
id: int
alert_type: str
latitude: float
longitude: float
address: Optional[str] = None
distance_km: float
created_at: datetime
responded_users_count: int = 0
class EmergencyStats(BaseModel):
total_alerts: int
active_alerts: int
resolved_alerts: int
avg_response_time_minutes: Optional[float]
total_responders: int
# Safety check schemas
class SafetyCheckCreate(BaseModel):
message: Optional[str] = Field(None, max_length=200)
location_latitude: Optional[float] = Field(None, ge=-90, le=90)
location_longitude: Optional[float] = Field(None, ge=-180, le=180)
class SafetyCheckResponse(BaseModel):
id: int
uuid: UUID
user_id: int
message: Optional[str] = None
location_latitude: Optional[float] = None
location_longitude: Optional[float] = None
created_at: datetime
class Config:
from_attributes = True

108
simple_test.sh Executable file
View File

@@ -0,0 +1,108 @@
#!/bin/bash
# Простой тест Emergency Service API
# Тестирование только работающих endpoints
# Цвета для вывода
GREEN='\033[0;32m'
RED='\033[0;31m'
BLUE='\033[0;34m'
NC='\033[0m'
success() { echo -e "${GREEN}$1${NC}"; }
error() { echo -e "${RED}$1${NC}"; }
info() { echo -e "${BLUE} $1${NC}"; }
BASE_URL="http://127.0.0.1:8002"
USER_URL="http://127.0.0.1:8001"
# Получение токена
info "1. Получение токена авторизации..."
LOGIN_RESPONSE=$(curl -s -X POST "${USER_URL}/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d '{"username":"apitestuser","password":"testpass123"}')
TOKEN=$(echo "$LOGIN_RESPONSE" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data['access_token'])" 2>/dev/null)
if [ -z "$TOKEN" ] || [ "$TOKEN" = "null" ]; then
error "Ошибка получения токена"
echo "$LOGIN_RESPONSE"
exit 1
fi
success "Токен получен: ${TOKEN:0:20}..."
# Тест 1: Health Check
info "2. Проверка работы сервиса..."
HEALTH_RESPONSE=$(curl -s "${BASE_URL}/health")
if echo "$HEALTH_RESPONSE" | grep -q "healthy"; then
success "Health Check прошел"
else
error "Health Check не прошел: $HEALTH_RESPONSE"
fi
# Тест 2: Создание оповещения
info "3. Создание экстренного оповещения..."
ALERT_RESPONSE=$(curl -s -X POST "${BASE_URL}/api/v1/alert" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"latitude": 55.7558,
"longitude": 37.6176,
"alert_type": "general",
"message": "API тест - экстренное оповещение"
}')
ALERT_ID=$(echo "$ALERT_RESPONSE" | python3 -c "import sys,json; data=json.load(sys.stdin); print(data['id'])" 2>/dev/null)
if [ -n "$ALERT_ID" ] && [ "$ALERT_ID" != "null" ]; then
success "Оповещение создано с ID: $ALERT_ID"
else
error "Ошибка создания оповещения: $ALERT_RESPONSE"
fi
# Тест 3: Получение статистики
info "4. Получение статистики..."
STATS_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/v1/stats" \
-H "Authorization: Bearer $TOKEN")
if echo "$STATS_RESPONSE" | grep -q "total_alerts"; then
success "Статистика получена"
echo "$STATS_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$STATS_RESPONSE"
else
error "Ошибка получения статистики: $STATS_RESPONSE"
fi
# Тест 4: Поиск ближайших оповещений
info "5. Поиск ближайших оповещений..."
NEARBY_RESPONSE=$(curl -s -X GET "${BASE_URL}/api/v1/alerts/nearby?latitude=55.7558&longitude=37.6176&radius_km=10" \
-H "Authorization: Bearer $TOKEN")
if echo "$NEARBY_RESPONSE" | grep -q "distance_km"; then
success "Ближайшие оповещения найдены"
echo "$NEARBY_RESPONSE" | python3 -m json.tool 2>/dev/null || echo "$NEARBY_RESPONSE"
else
error "Ошибка поиска ближайших оповещений: $NEARBY_RESPONSE"
fi
# Тест 5: Отклик на оповещение (если есть ID)
if [ -n "$ALERT_ID" ] && [ "$ALERT_ID" != "null" ]; then
info "6. Создание отклика на оповещение..."
RESPONSE_DATA=$(curl -s -X POST "${BASE_URL}/api/v1/alert/${ALERT_ID}/respond" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $TOKEN" \
-d '{
"response_type": "help_on_way",
"message": "Еду на помощь!",
"eta_minutes": 10
}')
if echo "$RESPONSE_DATA" | grep -q "response_type"; then
success "Отклик на оповещение создан"
echo "$RESPONSE_DATA" | python3 -m json.tool 2>/dev/null || echo "$RESPONSE_DATA"
else
error "Ошибка создания отклика: $RESPONSE_DATA"
fi
fi
info "Тестирование завершено!"

View File

@@ -1,118 +0,0 @@
#!/usr/bin/env python3
import json
import requests
import time
def test_registration_and_login():
"""Test registration and login flow"""
base_url = "http://localhost:8000"
# Test user data
test_user = {
"email": "testuser@example.com",
"username": "testuser123",
"password": "SecurePass123",
"first_name": "Test",
"last_name": "User"
}
print("🔧 Creating test user")
print("=" * 50)
# Try to register the user
try:
headers = {
"Content-Type": "application/json",
"Accept": "application/json"
}
print(f"Registering user: {test_user['email']}")
registration_response = requests.post(
f"{base_url}/api/v1/auth/register",
json=test_user,
headers=headers,
timeout=10
)
print(f"Registration Status: {registration_response.status_code}")
print(f"Registration Response: {registration_response.text}")
if registration_response.status_code == 200:
print("✅ User registered successfully")
elif registration_response.status_code == 400:
if "already registered" in registration_response.text.lower():
print(" User already exists, proceeding with login test")
else:
print(f"❌ Registration failed: {registration_response.text}")
return
else:
print(f"❌ Registration failed with status: {registration_response.status_code}")
return
time.sleep(1)
# Test login scenarios
login_tests = [
{
"name": "Login with email",
"data": {
"email": test_user["email"],
"password": test_user["password"]
}
},
{
"name": "Login with username",
"data": {
"username": test_user["username"],
"password": test_user["password"]
}
},
{
"name": "Login with wrong password",
"data": {
"email": test_user["email"],
"password": "wrongpassword"
}
}
]
for test in login_tests:
print(f"\n🧪 Testing: {test['name']}")
print("=" * 50)
try:
print(f"Login data: {json.dumps(test['data'], indent=2)}")
login_response = requests.post(
f"{base_url}/api/v1/auth/login",
json=test["data"],
headers=headers,
timeout=10
)
print(f"Login Status: {login_response.status_code}")
print(f"Login Response: {login_response.text}")
if login_response.status_code == 200:
try:
token_data = login_response.json()
print(f"✅ Login successful! Token type: {token_data.get('token_type')}")
print(f"Access token (first 20 chars): {token_data.get('access_token', '')[:20]}...")
except:
print("✅ Login successful but response parsing failed")
else:
print(f"❌ Login failed")
except Exception as e:
print(f"Login error: {str(e)}")
time.sleep(1)
except Exception as e:
print(f"Test error: {str(e)}")
if __name__ == "__main__":
print("🔍 Testing registration and login flow")
test_registration_and_login()

264
test_emergency_api.sh Executable file
View File

@@ -0,0 +1,264 @@
#!/bin/bash
# Emergency Service API Testing Script
# Скрипт для тестирования всех endpoints Emergency Service API
set -e # Остановка при ошибке
# Конфигурация
BASE_URL="http://127.0.0.1:8002"
GATEWAY_URL="http://127.0.0.1:8000"
USER_URL="http://127.0.0.1:8001"
# Цвета для вывода
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
BLUE='\033[0;34m'
NC='\033[0m' # No Color
# Функции для вывода
success() {
echo -e "${GREEN}$1${NC}"
}
error() {
echo -e "${RED}$1${NC}"
}
info() {
echo -e "${BLUE} $1${NC}"
}
warning() {
echo -e "${YELLOW}⚠️ $1${NC}"
}
# Функция для создания пользователя и получения токена
get_auth_token() {
info "Получение токена авторизации..."
# Создаем тестового пользователя
local timestamp=$(date +%s)
local test_username="testuser_${timestamp}"
local test_email="testuser_${timestamp}@example.com"
# Регистрируем пользователя
local register_response=$(curl -s -X POST "${USER_URL}/api/v1/auth/register" \
-H "Content-Type: application/json" \
-d "{
\"username\": \"${test_username}\",
\"email\": \"${test_email}\",
\"password\": \"testpass123\",
\"full_name\": \"Test User ${timestamp}\"
}")
if echo "$register_response" | grep -q '"id"'; then
success "Пользователь зарегистрирован: ${test_username}"
else
error "Ошибка регистрации: $register_response"
return 1
fi
# Получаем токен
local login_response=$(curl -s -X POST "${USER_URL}/api/v1/auth/login" \
-H "Content-Type: application/json" \
-d "{
\"username\": \"${test_username}\",
\"password\": \"testpass123\"
}")
local token=$(echo "$login_response" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4)
if [ -n "$token" ]; then
success "Токен получен"
echo "$token"
else
error "Ошибка получения токена: $login_response"
return 1
fi
}
# Функция для выполнения HTTP запросов
make_request() {
local method="$1"
local endpoint="$2"
local data="$3"
local token="$4"
if [ -n "$data" ]; then
curl -s -X "$method" "${BASE_URL}${endpoint}" \
-H "Content-Type: application/json" \
-H "Authorization: Bearer $token" \
-d "$data"
else
curl -s -X "$method" "${BASE_URL}${endpoint}" \
-H "Authorization: Bearer $token"
fi
}
# Функция для проверки ответа
check_response() {
local response="$1"
local test_name="$2"
# Проверяем статус код и содержание ответа
if [ -n "$response" ] && (echo "$response" | grep -q '"id"\\|"status":"healthy"\\|"message"\\|"access_token"\\|"uuid"' || [ "${response:0:1}" = "{" ]); then
success "$test_name: Успешно"
echo "$response" | jq . 2>/dev/null || echo "$response"
return 0
else
error "$test_name: Ошибка"
echo "$response"
return 1
fi
}
main() {
info "🚀 Начало тестирования Emergency Service API"
echo "=============================================="
# 1. Проверка здоровья сервиса
info "1. Проверка здоровья сервиса"
local health_response=$(curl -s "${BASE_URL}/health")
check_response "$health_response" "Health Check"
echo ""
# 2. Получение токена авторизации
info "2. Получение токена авторизации"
local auth_token
auth_token=$(get_auth_token)
if [ $? -ne 0 ]; then
error "Не удалось получить токен авторизации. Завершение тестов."
exit 1
fi
echo ""
# 3. Создание экстренного оповещения
info "3. Создание экстренного оповещения"
local alert_data='{
"latitude": 55.7558,
"longitude": 37.6176,
"alert_type": "general",
"message": "Тестовое оповещение - нужна помощь",
"address": "Красная площадь, Москва"
}'
local alert_response=$(make_request "POST" "/api/v1/alert" "$alert_data" "$auth_token")
if check_response "$alert_response" "Create Alert"; then
local alert_id=$(echo "$alert_response" | grep -o '"id":[0-9]*' | cut -d':' -f2)
success "Alert ID: $alert_id"
fi
echo ""
# 4. Получение статистики
info "4. Получение статистики"
local stats_response=$(make_request "GET" "/api/v1/stats" "" "$auth_token")
check_response "$stats_response" "Get Statistics"
echo ""
# 5. Получение активных оповещений
info "5. Получение активных оповещений"
local active_response=$(make_request "GET" "/api/v1/alerts/active" "" "$auth_token")
check_response "$active_response" "Get Active Alerts"
echo ""
# 6. Получение моих оповещений
info "6. Получение моих оповещений"
local my_alerts_response=$(make_request "GET" "/api/v1/alerts/my" "" "$auth_token")
check_response "$my_alerts_response" "Get My Alerts"
echo ""
# 7. Получение ближайших оповещений
info "7. Получение ближайших оповещений"
local nearby_response=$(make_request "GET" "/api/v1/alerts/nearby?latitude=55.7558&longitude=37.6176&radius_km=10" "" "$auth_token")
check_response "$nearby_response" "Get Nearby Alerts"
echo ""
# 8. Создание отчета о происшествии
info "8. Создание отчета о происшествии"
local report_data='{
"latitude": 55.7500,
"longitude": 37.6200,
"report_type": "harassment",
"description": "Тестовый отчет о происшествии в районе метро",
"address": "Тверская улица, Москва",
"severity": 3,
"is_anonymous": false
}'
local report_response=$(make_request "POST" "/api/v1/report" "$report_data" "$auth_token")
check_response "$report_response" "Create Report"
echo ""
# 9. Получение отчетов
info "9. Получение отчетов"
local reports_response=$(make_request "GET" "/api/v1/reports" "" "$auth_token")
check_response "$reports_response" "Get Reports"
echo ""
# 10. Создание отметки безопасности
info "10. Создание отметки безопасности"
local safety_data='{
"message": "Добрался домой, все хорошо",
"location_latitude": 55.7600,
"location_longitude": 37.6100
}'
local safety_response=$(make_request "POST" "/api/v1/safety-check" "$safety_data" "$auth_token")
check_response "$safety_response" "Create Safety Check"
echo ""
# 11. Получение отметок безопасности
info "11. Получение отметок безопасности"
local safety_checks_response=$(make_request "GET" "/api/v1/safety-checks" "" "$auth_token")
check_response "$safety_checks_response" "Get Safety Checks"
echo ""
# 12. Тест отклика на оповещение (если есть активные оповещения)
if [ -n "$alert_id" ] && [ "$alert_id" != "null" ]; then
info "12. Создание второго пользователя для отклика"
local second_token
second_token=$(get_auth_token)
if [ $? -eq 0 ]; then
info "13. Отклик на оповещение"
local response_data='{
"response_type": "help_on_way",
"message": "Еду к вам, буду через 15 минут!",
"eta_minutes": 15
}'
local respond_response=$(make_request "POST" "/api/v1/alert/${alert_id}/respond" "$response_data" "$second_token")
check_response "$respond_response" "Respond to Alert"
echo ""
# 14. Получение откликов на оповещение
info "14. Получение откликов на оповещение"
local responses_response=$(make_request "GET" "/api/v1/alert/${alert_id}/responses" "" "$auth_token")
check_response "$responses_response" "Get Alert Responses"
echo ""
# 15. Обновление оповещения
info "15. Обновление оповещения"
local update_data='{
"message": "Обновленное описание ситуации"
}'
local update_response=$(make_request "PUT" "/api/v1/alert/${alert_id}" "$update_data" "$auth_token")
check_response "$update_response" "Update Alert"
echo ""
# 16. Решение оповещения
info "16. Решение оповещения"
local resolve_response=$(make_request "PUT" "/api/v1/alert/${alert_id}/resolve" "" "$auth_token")
check_response "$resolve_response" "Resolve Alert"
echo ""
fi
fi
info "✅ Тестирование Emergency Service API завершено"
success "Все основные endpoints протестированы!"
}
# Запуск основной функции
main "$@"

View File

@@ -1,106 +0,0 @@
#!/usr/bin/env python3
import json
import requests
import time
def test_fixed_endpoints():
"""Test the fixed endpoints on production server"""
base_url = "http://192.168.0.103:8000"
# First, login to get access token
print("🔐 Step 1: Login to get access token")
login_response = requests.post(
f"{base_url}/api/v1/auth/login",
json={
"username": "Trevor1985",
"password": "Cl0ud_1985!"
},
headers={"Content-Type": "application/json"}
)
if login_response.status_code != 200:
print(f"❌ Login failed: {login_response.status_code} - {login_response.text}")
return
token_data = login_response.json()
access_token = token_data["access_token"]
print(f"✅ Login successful! Got access token.")
# Test endpoints that were failing
headers = {
"Authorization": f"Bearer {access_token}",
"Content-Type": "application/json"
}
test_endpoints = [
{
"name": "User Profile",
"url": "/api/v1/users/me",
"method": "GET"
},
{
"name": "User Dashboard (was 404)",
"url": "/api/v1/users/dashboard",
"method": "GET"
},
{
"name": "Emergency Contacts (was 500)",
"url": "/api/v1/users/me/emergency-contacts",
"method": "GET"
},
{
"name": "Profile (alternative endpoint)",
"url": "/api/v1/profile",
"method": "GET"
}
]
print(f"\n🧪 Step 2: Testing fixed endpoints")
print("=" * 60)
for endpoint in test_endpoints:
print(f"\n📍 Testing: {endpoint['name']}")
print(f" URL: {endpoint['url']}")
try:
if endpoint['method'] == 'GET':
response = requests.get(
f"{base_url}{endpoint['url']}",
headers=headers,
timeout=10
)
print(f" Status: {response.status_code}")
if response.status_code == 200:
try:
data = response.json()
if endpoint['name'] == "User Dashboard (was 404)":
print(f" ✅ Dashboard loaded! Emergency contacts: {data.get('emergency_contacts_count', 0)}")
print(f" User: {data.get('user', {}).get('username', 'N/A')}")
elif endpoint['name'] == "Emergency Contacts (was 500)":
contacts = data if isinstance(data, list) else []
print(f" ✅ Emergency contacts loaded! Count: {len(contacts)}")
else:
print(f" ✅ Success! Got user data.")
except:
print(f" ✅ Success! (Response not JSON)")
else:
print(f" ❌ Failed: {response.text[:100]}...")
except Exception as e:
print(f" 💥 Error: {str(e)}")
time.sleep(0.5)
print(f"\n{'='*60}")
print("📊 Summary:")
print("• Login: ✅ Working")
print("• Check each endpoint status above")
print("• All 422 login errors should be resolved")
print("• Database errors should be fixed")
if __name__ == "__main__":
print("🚀 Testing fixed endpoints on production server 192.168.0.103")
test_fixed_endpoints()

View File

@@ -1,83 +0,0 @@
#!/usr/bin/env python3
import json
import requests
import time
# Test login endpoint with detailed logging
def test_login():
"""Test login with various scenarios"""
base_url = "http://localhost:8000"
# Test cases
test_cases = [
{
"name": "Empty request",
"data": {}
},
{
"name": "Only password",
"data": {"password": "testpass123"}
},
{
"name": "Only email",
"data": {"email": "test@example.com"}
},
{
"name": "Valid email and password",
"data": {
"email": "test@example.com",
"password": "testpass123"
}
},
{
"name": "Valid username and password",
"data": {
"username": "testuser",
"password": "testpass123"
}
},
{
"name": "Invalid JSON format",
"data": "invalid json",
"content_type": "text/plain"
}
]
for test_case in test_cases:
print(f"\n🧪 Testing: {test_case['name']}")
print("=" * 50)
try:
headers = {
"Content-Type": test_case.get("content_type", "application/json"),
"Accept": "application/json"
}
if isinstance(test_case["data"], dict):
data = json.dumps(test_case["data"])
print(f"Request data: {data}")
else:
data = test_case["data"]
print(f"Request data (raw): {data}")
response = requests.post(
f"{base_url}/api/v1/auth/login",
data=data,
headers=headers,
timeout=10
)
print(f"Status Code: {response.status_code}")
print(f"Headers: {dict(response.headers)}")
print(f"Response: {response.text}")
except Exception as e:
print(f"Error: {str(e)}")
time.sleep(1) # Wait between requests
if __name__ == "__main__":
print("🔍 Testing login endpoint with detailed logging")
print("Make sure to check the server logs for debugging info")
test_login()

View File

@@ -1,149 +0,0 @@
#!/usr/bin/env python3
import json
import requests
import time
def test_mobile_app_formats():
"""Test various data formats that mobile apps commonly send"""
base_url = "http://192.168.0.103:8000"
# Common mobile app data format issues
test_cases = [
{
"name": "Android app - nested data structure",
"data": {
"user": {
"email": "testuser@example.com",
"password": "SecurePass123"
}
}
},
{
"name": "iOS app - camelCase fields",
"data": {
"emailAddress": "testuser@example.com",
"userPassword": "SecurePass123"
}
},
{
"name": "React Native - mixed case",
"data": {
"Email": "testuser@example.com",
"Password": "SecurePass123"
}
},
{
"name": "Flutter app - snake_case",
"data": {
"user_email": "testuser@example.com",
"user_password": "SecurePass123"
}
},
{
"name": "Mobile app with extra fields",
"data": {
"email": "testuser@example.com",
"password": "SecurePass123",
"device_id": "mobile123",
"app_version": "1.0.0",
"platform": "android"
}
},
{
"name": "Empty string fields (common mobile bug)",
"data": {
"email": "",
"username": "",
"password": "SecurePass123"
}
},
{
"name": "Null fields (another common mobile bug)",
"data": {
"email": None,
"username": None,
"password": "SecurePass123"
}
},
{
"name": "Correct format",
"data": {
"email": "testuser@example.com",
"password": "SecurePass123"
}
}
]
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "WomenSafetyApp/1.0 (Android)"
}
print("📱 Testing mobile app data formats on 192.168.0.103")
print("=" * 70)
for i, test_case in enumerate(test_cases, 1):
print(f"\n{i}. 🧪 {test_case['name']}")
print("-" * 50)
try:
# Send request
response = requests.post(
f"{base_url}/api/v1/auth/login",
json=test_case["data"],
headers=headers,
timeout=10
)
print(f"📤 Request: {json.dumps(test_case['data'], indent=2)}")
print(f"📊 Status: {response.status_code}")
# Analyze response
if response.status_code == 200:
print("✅ SUCCESS - Login worked!")
try:
token_data = response.json()
print(f"🔐 Token type: {token_data.get('token_type')}")
except:
pass
elif response.status_code == 422:
print("❌ VALIDATION ERROR")
try:
error_data = response.json()
if "detail" in error_data:
detail = error_data["detail"]
if isinstance(detail, list):
print("📝 Validation issues:")
for error in detail:
field = error.get("loc", [])[-1] if error.get("loc") else "unknown"
msg = error.get("msg", "Unknown error")
input_val = error.get("input", "")
print(f" • Field '{field}': {msg} (input: {input_val})")
else:
print(f"📝 Error: {detail}")
except Exception as e:
print(f"📝 Raw error: {response.text}")
elif response.status_code == 401:
print("🔒 AUTHENTICATION FAILED - Wrong credentials")
else:
print(f"🚫 OTHER ERROR: {response.status_code}")
print(f"📝 Response: {response.text[:200]}")
except Exception as e:
print(f"💥 REQUEST ERROR: {str(e)}")
time.sleep(0.5)
print(f"\n{'='*70}")
print("📋 SUMMARY:")
print("• Check which format works correctly")
print("• Compare with mobile app's actual request format")
print("• Update mobile app to match working format")
if __name__ == "__main__":
test_mobile_app_formats()

View File

@@ -1,100 +0,0 @@
#!/usr/bin/env python3
import json
import requests
import time
def test_production_login():
"""Test login endpoint on production server"""
base_url = "http://192.168.0.103:8000"
# Test cases that might be coming from mobile app
test_cases = [
{
"name": "Empty request (like from emulator)",
"data": {}
},
{
"name": "Only password",
"data": {"password": "testpass123"}
},
{
"name": "Email with extra spaces",
"data": {
"email": " testuser@example.com ",
"password": "SecurePass123"
}
},
{
"name": "Valid login data",
"data": {
"email": "testuser@example.com",
"password": "SecurePass123"
}
},
{
"name": "Username login",
"data": {
"username": "testuser123",
"password": "SecurePass123"
}
},
{
"name": "Invalid JSON structure (common mobile app error)",
"data": '{"email":"test@example.com","password":"test123"',
"raw": True
}
]
headers = {
"Content-Type": "application/json",
"Accept": "application/json",
"User-Agent": "MobileApp/1.0"
}
for test_case in test_cases:
print(f"\n🧪 Testing: {test_case['name']}")
print("=" * 60)
try:
if test_case.get("raw"):
data = test_case["data"]
print(f"Raw data: {data}")
else:
data = json.dumps(test_case["data"])
print(f"JSON data: {data}")
response = requests.post(
f"{base_url}/api/v1/auth/login",
data=data,
headers=headers,
timeout=10
)
print(f"Status: {response.status_code}")
print(f"Response: {response.text[:200]}...")
if response.status_code == 422:
print("🔍 This is a validation error - checking details...")
try:
error_details = response.json()
if "detail" in error_details:
detail = error_details["detail"]
if isinstance(detail, list):
for error in detail:
field = error.get("loc", [])[-1] if error.get("loc") else "unknown"
msg = error.get("msg", "Unknown error")
print(f" Field '{field}': {msg}")
else:
print(f" Error: {detail}")
except:
pass
except Exception as e:
print(f"Request error: {str(e)}")
time.sleep(1)
if __name__ == "__main__":
print("🔍 Testing login endpoint on production server 192.168.0.103")
test_production_login()

1
token.env Normal file
View File

@@ -0,0 +1 @@
TOKEN=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOCIsImVtYWlsIjoiYXBpdGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTc1ODg1NzA3Nn0.HO3jIZIzAFz0ojGVmExnnrwu5bmw42nMVnnCDiu9xHI

View File

@@ -1,29 +1,30 @@
Metadata-Version: 2.2
Metadata-Version: 2.1
Name: bcrypt
Version: 4.3.0
Version: 4.0.1
Summary: Modern password hashing for your software and your servers
Author-email: The Python Cryptographic Authority developers <cryptography-dev@python.org>
License: Apache-2.0
Project-URL: homepage, https://github.com/pyca/bcrypt/
Home-page: https://github.com/pyca/bcrypt/
Author: The Python Cryptographic Authority developers
Author-email: cryptography-dev@python.org
License: Apache License, Version 2.0
Platform: UNKNOWN
Classifier: Development Status :: 5 - Production/Stable
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Programming Language :: Python :: Implementation :: CPython
Classifier: Programming Language :: Python :: Implementation :: PyPy
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
Classifier: Programming Language :: Python :: 3.6
Classifier: Programming Language :: Python :: 3.7
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Requires-Python: >=3.8
Requires-Python: >=3.6
Description-Content-Type: text/x-rst
License-File: LICENSE
Provides-Extra: tests
Requires-Dist: pytest!=3.3.0,>=3.2.1; extra == "tests"
Requires-Dist: pytest (!=3.3.0,>=3.2.1) ; extra == 'tests'
Provides-Extra: typecheck
Requires-Dist: mypy; extra == "typecheck"
Requires-Dist: mypy ; extra == 'typecheck'
bcrypt
======
@@ -44,7 +45,7 @@ Installation
To install bcrypt, simply:
.. code:: console
.. code:: bash
$ pip install bcrypt
@@ -53,19 +54,19 @@ compiler and a Rust compiler (the minimum supported Rust version is 1.56.0).
For Debian and Ubuntu, the following command will ensure that the required dependencies are installed:
.. code:: console
.. code:: bash
$ sudo apt-get install build-essential cargo
For Fedora and RHEL-derivatives, the following command will ensure that the required dependencies are installed:
.. code:: console
.. code:: bash
$ sudo yum install gcc cargo
For Alpine, the following command will ensure that the required dependencies are installed:
.. code:: console
.. code:: bash
$ apk add --update musl-dev gcc cargo
@@ -78,52 +79,6 @@ While bcrypt remains an acceptable choice for password storage, depending on you
Changelog
=========
Unreleased
----------
* Dropped support for Python 3.7.
* We now support free-threaded Python 3.13.
* We now support PyPy 3.11.
* We now publish wheels for free-threaded Python 3.13, for PyPy 3.11 on
``manylinux``, and for ARMv7l on ``manylinux``.
4.2.1
-----
* Bump Rust dependency versions - this should resolve crashes on Python 3.13
free-threaded builds.
* We no longer build ``manylinux`` wheels for PyPy 3.9.
4.2.0
-----
* Bump Rust dependency versions
* Removed the ``BCRYPT_ALLOW_RUST_163`` environment variable.
4.1.3
-----
* Bump Rust dependency versions
4.1.2
-----
* Publish both ``py37`` and ``py39`` wheels. This should resolve some errors
relating to initializing a module multiple times per process.
4.1.1
-----
* Fixed the type signature on the ``kdf`` method.
* Fixed packaging bug on Windows.
* Fixed incompatibility with passlib package detection assumptions.
4.1.0
-----
* Dropped support for Python 3.6.
* Bumped MSRV to 1.64. (Note: Rust 1.63 can be used by setting the ``BCRYPT_ALLOW_RUST_163`` environment variable)
4.0.1
-----
@@ -316,7 +271,12 @@ Compatibility
-------------
This library should be compatible with py-bcrypt and it will run on Python
3.8+ (including free-threaded builds), and PyPy 3.
3.6+, and PyPy 3.
C Code
------
This library uses code from OpenBSD.
Security
--------
@@ -328,3 +288,5 @@ identify a vulnerability, we ask you to contact us privately.
.. _`standard library`: https://docs.python.org/3/library/hashlib.html#hashlib.scrypt
.. _`argon2_cffi`: https://argon2-cffi.readthedocs.io
.. _`cryptography`: https://cryptography.io/en/latest/hazmat/primitives/key-derivation-functions/#cryptography.hazmat.primitives.kdf.scrypt.Scrypt

View File

@@ -0,0 +1,14 @@
bcrypt-4.0.1.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
bcrypt-4.0.1.dist-info/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
bcrypt-4.0.1.dist-info/METADATA,sha256=peZwWFa95xnpp4NiIE7gJkV01CTkbVXIzoEN66SXd3c,8972
bcrypt-4.0.1.dist-info/RECORD,,
bcrypt-4.0.1.dist-info/REQUESTED,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
bcrypt-4.0.1.dist-info/WHEEL,sha256=ZXaM-AC_dnzk1sUAdQV_bMrIMG6zI-GthFaEkNkWsgU,112
bcrypt-4.0.1.dist-info/top_level.txt,sha256=BkR_qBzDbSuycMzHWE1vzXrfYecAzUVmQs6G2CukqNI,7
bcrypt/__about__.py,sha256=F7i0CQOa8G3Yjw1T71jQv8yi__Z_4TzLyZJv1GFqVx0,1320
bcrypt/__init__.py,sha256=EpUdbfHaiHlSoaM-SSUB6MOgNpWOIkS0ZrjxogPIRLM,3781
bcrypt/__pycache__/__about__.cpython-312.pyc,,
bcrypt/__pycache__/__init__.cpython-312.pyc,,
bcrypt/_bcrypt.abi3.so,sha256=_T-y5IrekziUzkYio4hWH7Xzw92XBKewSLd8kmERhGU,1959696
bcrypt/_bcrypt.pyi,sha256=O-vvHdooGyAxIkdKemVqOzBF5aMhh0evPSaDMgETgEk,214
bcrypt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

View File

@@ -0,0 +1,5 @@
Wheel-Version: 1.0
Generator: bdist_wheel (0.37.1)
Root-Is-Purelib: false
Tag: cp36-abi3-manylinux_2_28_x86_64

View File

@@ -1,11 +0,0 @@
bcrypt-4.3.0.dist-info/INSTALLER,sha256=zuuue4knoyJ-UwPPXg8fezS7VCrXJQrAP7zeNuwvFQg,4
bcrypt-4.3.0.dist-info/LICENSE,sha256=gXPVwptPlW1TJ4HSuG5OMPg-a3h43OGMkZRR1rpwfJA,10850
bcrypt-4.3.0.dist-info/METADATA,sha256=95qX7ziIfmOF0kNM95YZuWhLVfFy-6EtssVvf1ZgeWg,10042
bcrypt-4.3.0.dist-info/RECORD,,
bcrypt-4.3.0.dist-info/WHEEL,sha256=XlovOtcAZFqrc4OSNBtc5R3yDeRHyhWP24RdDnylFpY,111
bcrypt-4.3.0.dist-info/top_level.txt,sha256=BkR_qBzDbSuycMzHWE1vzXrfYecAzUVmQs6G2CukqNI,7
bcrypt/__init__.py,sha256=cv-NupIX6P7o6A4PK_F0ur6IZoDr3GnvyzFO9k16wKQ,1000
bcrypt/__init__.pyi,sha256=ITUCB9mPVU8sKUbJQMDUH5YfQXZb1O55F9qvKZR_o8I,333
bcrypt/__pycache__/__init__.cpython-312.pyc,,
bcrypt/_bcrypt.abi3.so,sha256=oMArVCuY_atg2H4SGNfM-zbfEgUOkd4qSiWn2nPqmXc,644928
bcrypt/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0

View File

@@ -1,5 +0,0 @@
Wheel-Version: 1.0
Generator: setuptools (75.8.2)
Root-Is-Purelib: false
Tag: cp39-abi3-manylinux_2_34_x86_64

View File

@@ -0,0 +1,41 @@
# Author:: Donald Stufft (<donald@stufft.io>)
# Copyright:: Copyright (c) 2013 Donald Stufft
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
from __future__ import division
from __future__ import unicode_literals
__all__ = [
"__title__",
"__summary__",
"__uri__",
"__version__",
"__author__",
"__email__",
"__license__",
"__copyright__",
]
__title__ = "bcrypt"
__summary__ = "Modern password hashing for your software and your servers"
__uri__ = "https://github.com/pyca/bcrypt/"
__version__ = "4.0.1"
__author__ = "The Python Cryptographic Authority developers"
__email__ = "cryptography-dev@python.org"
__license__ = "Apache License, Version 2.0"
__copyright__ = "Copyright 2013-2022 {0}".format(__author__)

View File

@@ -1,3 +1,7 @@
# Author:: Donald Stufft (<donald@stufft.io>)
# Copyright:: Copyright (c) 2013 Donald Stufft
# License:: Apache License, Version 2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
@@ -9,8 +13,14 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
from __future__ import absolute_import
from __future__ import division
from ._bcrypt import (
import hmac
import os
import warnings
from .__about__ import (
__author__,
__copyright__,
__email__,
@@ -18,26 +28,100 @@ from ._bcrypt import (
__summary__,
__title__,
__uri__,
checkpw,
gensalt,
hashpw,
kdf,
)
from ._bcrypt import (
__version_ex__ as __version__,
__version__,
)
from . import _bcrypt # noqa: I100
__all__ = [
"__author__",
"__copyright__",
"__email__",
"__license__",
"__summary__",
"__title__",
"__summary__",
"__uri__",
"__version__",
"checkpw",
"__author__",
"__email__",
"__license__",
"__copyright__",
"gensalt",
"hashpw",
"kdf",
"checkpw",
]
def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes:
if prefix not in (b"2a", b"2b"):
raise ValueError("Supported prefixes are b'2a' or b'2b'")
if rounds < 4 or rounds > 31:
raise ValueError("Invalid rounds")
salt = os.urandom(16)
output = _bcrypt.encode_base64(salt)
return (
b"$"
+ prefix
+ b"$"
+ ("%2.2u" % rounds).encode("ascii")
+ b"$"
+ output
)
def hashpw(password: bytes, salt: bytes) -> bytes:
if isinstance(password, str) or isinstance(salt, str):
raise TypeError("Strings must be encoded before hashing")
# bcrypt originally suffered from a wraparound bug:
# http://www.openwall.com/lists/oss-security/2012/01/02/4
# This bug was corrected in the OpenBSD source by truncating inputs to 72
# bytes on the updated prefix $2b$, but leaving $2a$ unchanged for
# compatibility. However, pyca/bcrypt 2.0.0 *did* correctly truncate inputs
# on $2a$, so we do it here to preserve compatibility with 2.0.0
password = password[:72]
return _bcrypt.hashpass(password, salt)
def checkpw(password: bytes, hashed_password: bytes) -> bool:
if isinstance(password, str) or isinstance(hashed_password, str):
raise TypeError("Strings must be encoded before checking")
ret = hashpw(password, hashed_password)
return hmac.compare_digest(ret, hashed_password)
def kdf(
password: bytes,
salt: bytes,
desired_key_bytes: int,
rounds: int,
ignore_few_rounds: bool = False,
) -> bytes:
if isinstance(password, str) or isinstance(salt, str):
raise TypeError("Strings must be encoded before hashing")
if len(password) == 0 or len(salt) == 0:
raise ValueError("password and salt must not be empty")
if desired_key_bytes <= 0 or desired_key_bytes > 512:
raise ValueError("desired_key_bytes must be 1-512")
if rounds < 1:
raise ValueError("rounds must be 1 or more")
if rounds < 50 and not ignore_few_rounds:
# They probably think bcrypt.kdf()'s rounds parameter is logarithmic,
# expecting this value to be slow enough (it probably would be if this
# were bcrypt). Emit a warning.
warnings.warn(
(
"Warning: bcrypt.kdf() called with only {0} round(s). "
"This few is not secure: the parameter is linear, like PBKDF2."
).format(rounds),
UserWarning,
stacklevel=2,
)
return _bcrypt.pbkdf(password, salt, rounds, desired_key_bytes)

View File

@@ -1,10 +0,0 @@
def gensalt(rounds: int = 12, prefix: bytes = b"2b") -> bytes: ...
def hashpw(password: bytes, salt: bytes) -> bytes: ...
def checkpw(password: bytes, hashed_password: bytes) -> bool: ...
def kdf(
password: bytes,
salt: bytes,
desired_key_bytes: int,
rounds: int,
ignore_few_rounds: bool = False,
) -> bytes: ...

View File

@@ -0,0 +1,7 @@
import typing
def encode_base64(data: bytes) -> bytes: ...
def hashpass(password: bytes, salt: bytes) -> bytes: ...
def pbkdf(
password: bytes, salt: bytes, rounds: int, desired_key_bytes: int
) -> bytes: ...