This commit is contained in:
254
docs/MOBILE_API.md
Normal file
254
docs/MOBILE_API.md
Normal file
@@ -0,0 +1,254 @@
|
|||||||
|
# Инструкция по интеграции мобильного API календарного сервиса
|
||||||
|
|
||||||
|
## Обзор
|
||||||
|
|
||||||
|
Этот документ содержит информацию о том, как интегрировать мобильное приложение с API календарного сервиса. Календарный сервис предоставляет эндпоинты для создания, получения и удаления записей в календаре женского здоровья.
|
||||||
|
|
||||||
|
## Базовый URL
|
||||||
|
|
||||||
|
```
|
||||||
|
http://[domain]/api/v1/calendar/
|
||||||
|
```
|
||||||
|
|
||||||
|
## Аутентификация
|
||||||
|
|
||||||
|
Все запросы к API должны включать токен аутентификации в заголовке `Authorization`:
|
||||||
|
|
||||||
|
```
|
||||||
|
Authorization: Bearer [token]
|
||||||
|
```
|
||||||
|
|
||||||
|
Для получения токена аутентификации используйте сервис аутентификации:
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/auth/token
|
||||||
|
Content-Type: application/x-www-form-urlencoded
|
||||||
|
|
||||||
|
username=user@example.com&password=userpassword
|
||||||
|
```
|
||||||
|
|
||||||
|
## Эндпоинты для мобильного приложения
|
||||||
|
|
||||||
|
### 1. Создание записи в календаре
|
||||||
|
|
||||||
|
```
|
||||||
|
POST /api/v1/calendar/entries/mobile
|
||||||
|
Content-Type: application/json
|
||||||
|
Authorization: Bearer [token]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Формат запроса
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"date": "2025-09-26",
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Пример записи"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Параметры
|
||||||
|
|
||||||
|
| Параметр | Тип | Обязательный | Описание |
|
||||||
|
|----------------|--------------|--------------|--------------------------------------------------------------------------------------|
|
||||||
|
| date | string | Да | Дата записи в формате YYYY-MM-DD |
|
||||||
|
| type | string | Да | Тип записи: MENSTRUATION, OVULATION, SPOTTING, DISCHARGE, PAIN, MOOD |
|
||||||
|
| flow_intensity | integer | Нет | Интенсивность выделений по шкале 1-5 (только для MENSTRUATION и SPOTTING) |
|
||||||
|
| symptoms | array[string]| Нет | Массив симптомов: CRAMPS, HEADACHE, BLOATING, FATIGUE, и т.д. |
|
||||||
|
| mood | string | Нет | Настроение: HAPPY, SAD, NORMAL, STRESSED, ANXIOUS, IRRITATED |
|
||||||
|
| notes | string | Нет | Текстовая заметка |
|
||||||
|
|
||||||
|
#### Пример успешного ответа
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"entry_date": "2025-09-26",
|
||||||
|
"entry_type": "period",
|
||||||
|
"flow_intensity": "medium",
|
||||||
|
"period_symptoms": "",
|
||||||
|
"mood": "happy",
|
||||||
|
"energy_level": 1,
|
||||||
|
"sleep_hours": 0,
|
||||||
|
"symptoms": "CRAMPS, HEADACHE",
|
||||||
|
"medications": "",
|
||||||
|
"notes": "Пример записи",
|
||||||
|
"is_predicted": false,
|
||||||
|
"confidence_score": null,
|
||||||
|
"created_at": "2023-09-26T14:30:00.000Z"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. Получение записей календаря
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/calendar/entries
|
||||||
|
Authorization: Bearer [token]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Параметры запроса (в URL)
|
||||||
|
|
||||||
|
| Параметр | Тип | Обязательный | Описание |
|
||||||
|
|-------------|--------|--------------|------------------------------------------|
|
||||||
|
| start_date | string | Нет | Начальная дата в формате YYYY-MM-DD |
|
||||||
|
| end_date | string | Нет | Конечная дата в формате YYYY-MM-DD |
|
||||||
|
| entry_type | string | Нет | Тип записи (period, ovulation, symptoms) |
|
||||||
|
| limit | integer| Нет | Ограничение количества возвращаемых записей (по умолчанию 100) |
|
||||||
|
|
||||||
|
#### Пример успешного ответа
|
||||||
|
|
||||||
|
```json
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"id": 123,
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440000",
|
||||||
|
"entry_date": "2025-09-26",
|
||||||
|
"entry_type": "period",
|
||||||
|
"flow_intensity": "medium",
|
||||||
|
"period_symptoms": "",
|
||||||
|
"mood": "happy",
|
||||||
|
"energy_level": 1,
|
||||||
|
"sleep_hours": 0,
|
||||||
|
"symptoms": "CRAMPS, HEADACHE",
|
||||||
|
"medications": "",
|
||||||
|
"notes": "Пример записи",
|
||||||
|
"is_predicted": false,
|
||||||
|
"confidence_score": null,
|
||||||
|
"created_at": "2023-09-26T14:30:00.000Z"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": 124,
|
||||||
|
"uuid": "550e8400-e29b-41d4-a716-446655440001",
|
||||||
|
"entry_date": "2025-09-30",
|
||||||
|
"entry_type": "ovulation",
|
||||||
|
"flow_intensity": null,
|
||||||
|
"period_symptoms": "",
|
||||||
|
"mood": "happy",
|
||||||
|
"energy_level": 1,
|
||||||
|
"sleep_hours": 0,
|
||||||
|
"symptoms": "BREAST_TENDERNESS",
|
||||||
|
"medications": "",
|
||||||
|
"notes": "Пример записи об овуляции",
|
||||||
|
"is_predicted": false,
|
||||||
|
"confidence_score": null,
|
||||||
|
"created_at": "2023-09-26T14:30:00.000Z"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3. Удаление записи календаря
|
||||||
|
|
||||||
|
```
|
||||||
|
DELETE /api/v1/entries/{entry_id}
|
||||||
|
Authorization: Bearer [token]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Параметры
|
||||||
|
|
||||||
|
| Параметр | Тип | Обязательный | Описание |
|
||||||
|
|-----------|---------|--------------|------------------------|
|
||||||
|
| entry_id | integer | Да | ID записи для удаления |
|
||||||
|
|
||||||
|
#### Пример успешного ответа
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"message": "Entry deleted successfully"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 4. Получение обзора цикла
|
||||||
|
|
||||||
|
```
|
||||||
|
GET /api/v1/cycle-overview
|
||||||
|
Authorization: Bearer [token]
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Пример успешного ответа
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"current_cycle_day": 14,
|
||||||
|
"current_phase": "ovulation",
|
||||||
|
"next_period_date": "2023-10-15",
|
||||||
|
"days_until_period": 12,
|
||||||
|
"cycle_regularity": "regular",
|
||||||
|
"avg_cycle_length": 28
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Маппинг типов данных
|
||||||
|
|
||||||
|
### Типы записей
|
||||||
|
|
||||||
|
| Мобильное приложение | Сервер |
|
||||||
|
|----------------------|--------------|
|
||||||
|
| MENSTRUATION | period |
|
||||||
|
| OVULATION | ovulation |
|
||||||
|
| SPOTTING | symptoms |
|
||||||
|
| DISCHARGE | symptoms |
|
||||||
|
| PAIN | symptoms |
|
||||||
|
| MOOD | mood |
|
||||||
|
|
||||||
|
### Интенсивность выделений
|
||||||
|
|
||||||
|
| Мобильное приложение | Сервер |
|
||||||
|
|----------------------|--------------|
|
||||||
|
| 1 | light |
|
||||||
|
| 2 | light |
|
||||||
|
| 3 | medium |
|
||||||
|
| 4 | heavy |
|
||||||
|
| 5 | heavy |
|
||||||
|
|
||||||
|
### Настроение
|
||||||
|
|
||||||
|
| Мобильное приложение | Сервер |
|
||||||
|
|----------------------|--------------|
|
||||||
|
| HAPPY | happy |
|
||||||
|
| SAD | sad |
|
||||||
|
| NORMAL | happy |
|
||||||
|
| STRESSED | anxious |
|
||||||
|
| ANXIOUS | anxious |
|
||||||
|
| IRRITATED | irritated |
|
||||||
|
|
||||||
|
## Тестирование API
|
||||||
|
|
||||||
|
Для тестирования API можно использовать скрипты:
|
||||||
|
|
||||||
|
1. `setup_mobile_test.py` - создание токена для тестирования
|
||||||
|
2. `mobile_format_test.py` - тестирование преобразования форматов данных
|
||||||
|
3. `test_mobile_api.py` - полное тестирование мобильного API
|
||||||
|
|
||||||
|
### Пример запроса с curl
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -X POST http://localhost:8004/api/v1/calendar/entries/mobile \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $(cat auth_token.txt)" \
|
||||||
|
-d '{
|
||||||
|
"date": "2025-09-26",
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тестовая запись"
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
## Коды ошибок
|
||||||
|
|
||||||
|
| Код | Описание |
|
||||||
|
|------|---------------------------------------|
|
||||||
|
| 400 | Неверный запрос или данные |
|
||||||
|
| 401 | Ошибка аутентификации |
|
||||||
|
| 404 | Запись не найдена |
|
||||||
|
| 422 | Ошибка валидации данных |
|
||||||
|
| 500 | Внутренняя ошибка сервера |
|
||||||
|
|
||||||
|
## Контактная информация
|
||||||
|
|
||||||
|
При возникновении вопросов или проблем с интеграцией, пожалуйста, свяжитесь с командой разработки.
|
||||||
7
mobile_api_example.sh
Normal file
7
mobile_api_example.sh
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Пример запроса к мобильному API календарного сервиса
|
||||||
|
curl -v -X POST http://localhost:8004/api/v1/calendar/entries/mobile \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-H "Authorization: Bearer $(cat auth_token.txt)" \
|
||||||
|
-d '{"date": "2025-09-26", "type": "MENSTRUATION", "flow_intensity": 3, "symptoms": ["CRAMPS", "HEADACHE"], "mood": "NORMAL", "notes": "Тестовая запись"}'
|
||||||
139
mobile_format_test.py
Executable file
139
mobile_format_test.py
Executable file
@@ -0,0 +1,139 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
"""
|
||||||
|
Скрипт для демонстрации преобразования данных мобильного формата в формат сервера.
|
||||||
|
Показывает, как преобразовывать данные и какие поля ожидает сервер.
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from datetime import date, datetime
|
||||||
|
|
||||||
|
# Пример данных из мобильного приложения
|
||||||
|
MOBILE_DATA_EXAMPLES = [
|
||||||
|
{
|
||||||
|
"date": "2025-09-26",
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Пример записи о менструации"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2025-09-30",
|
||||||
|
"type": "OVULATION",
|
||||||
|
"symptoms": ["BREAST_TENDERNESS"],
|
||||||
|
"mood": "HAPPY",
|
||||||
|
"notes": "Пример записи об овуляции"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"date": "2025-10-05",
|
||||||
|
"type": "SPOTTING",
|
||||||
|
"flow_intensity": 1,
|
||||||
|
"symptoms": ["BLOATING"],
|
||||||
|
"mood": "STRESSED",
|
||||||
|
"notes": "Пример записи о выделениях"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
|
||||||
|
def test_format_conversion(mobile_data):
|
||||||
|
"""Тестирование преобразования формата данных"""
|
||||||
|
url = "http://localhost:8004/debug/mobile-entry"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"\nТестирование преобразования данных: {json.dumps(mobile_data, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=mobile_data, headers=headers)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
result = response.json()
|
||||||
|
|
||||||
|
print("✅ Успешное преобразование данных")
|
||||||
|
print("\nОригинальные данные (мобильный формат):")
|
||||||
|
print(json.dumps(result["original_data"], ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
print("\nПреобразованные данные (формат сервера):")
|
||||||
|
print(json.dumps(result["transformed_data"], ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
print("\nАнализ преобразования:")
|
||||||
|
print(f"- Дата: {mobile_data['date']} -> {result['transformed_data']['entry_date']}")
|
||||||
|
print(f"- Тип: {mobile_data['type']} -> {result['transformed_data']['entry_type']}")
|
||||||
|
|
||||||
|
if mobile_data.get('flow_intensity'):
|
||||||
|
print(f"- Интенсивность: {mobile_data['flow_intensity']} -> {result['transformed_data']['flow_intensity']}")
|
||||||
|
|
||||||
|
if mobile_data.get('symptoms'):
|
||||||
|
print(f"- Симптомы: {', '.join(mobile_data['symptoms'])} -> {result['transformed_data']['symptoms']}")
|
||||||
|
|
||||||
|
if mobile_data.get('mood'):
|
||||||
|
print(f"- Настроение: {mobile_data['mood']} -> {result['transformed_data']['mood'] or 'Не задано'}")
|
||||||
|
|
||||||
|
return result
|
||||||
|
else:
|
||||||
|
print(f"❌ Ошибка: {response.status_code}")
|
||||||
|
print(response.text)
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Исключение: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def generate_documentation():
|
||||||
|
"""Генерация документации по формату данных"""
|
||||||
|
print("\n=== ДОКУМЕНТАЦИЯ ПО ФОРМАТУ ДАННЫХ ===\n")
|
||||||
|
print("Мобильный формат данных должен соответствовать следующей схеме:")
|
||||||
|
|
||||||
|
schema = {
|
||||||
|
"date": "string (формат YYYY-MM-DD, например '2025-09-26')",
|
||||||
|
"type": "string (один из: MENSTRUATION, OVULATION, SPOTTING, DISCHARGE, PAIN, MOOD)",
|
||||||
|
"flow_intensity": "integer (опционально, значения от 1 до 5)",
|
||||||
|
"symptoms": "массив строк (опционально, например ['CRAMPS', 'HEADACHE'])",
|
||||||
|
"mood": "string (опционально, одно из: HAPPY, SAD, NORMAL, STRESSED, ANXIOUS, IRRITATED)",
|
||||||
|
"notes": "string (опционально, текстовые заметки)"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(schema, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
print("\nМаппинг между форматами:")
|
||||||
|
|
||||||
|
mapping = {
|
||||||
|
"Типы записей": {
|
||||||
|
"MENSTRUATION": "period",
|
||||||
|
"OVULATION": "ovulation",
|
||||||
|
"SPOTTING": "symptoms",
|
||||||
|
"DISCHARGE": "symptoms",
|
||||||
|
"PAIN": "symptoms",
|
||||||
|
"MOOD": "mood"
|
||||||
|
},
|
||||||
|
"Интенсивность выделений": {
|
||||||
|
"1": "light",
|
||||||
|
"2": "light",
|
||||||
|
"3": "medium",
|
||||||
|
"4-5": "heavy"
|
||||||
|
},
|
||||||
|
"Настроение": {
|
||||||
|
"HAPPY": "happy",
|
||||||
|
"SAD": "sad",
|
||||||
|
"NORMAL": "happy", # Маппится на happy
|
||||||
|
"STRESSED": "anxious",
|
||||||
|
"ANXIOUS": "anxious",
|
||||||
|
"IRRITATED": "irritated"
|
||||||
|
},
|
||||||
|
"Симптомы": "Преобразуются из массива строк в строку с разделителями-запятыми"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(json.dumps(mapping, ensure_ascii=False, indent=2))
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=== ТЕСТИРОВАНИЕ ПРЕОБРАЗОВАНИЯ ДАННЫХ МОБИЛЬНОГО ФОРМАТА ===\n")
|
||||||
|
|
||||||
|
for i, example in enumerate(MOBILE_DATA_EXAMPLES):
|
||||||
|
print(f"\n--- Пример #{i+1} ---")
|
||||||
|
test_format_conversion(example)
|
||||||
|
|
||||||
|
generate_documentation()
|
||||||
|
|
||||||
|
print("\n=== ТЕСТИРОВАНИЕ ЗАВЕРШЕНО ===")
|
||||||
@@ -13,6 +13,7 @@ from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntr
|
|||||||
CycleDataResponse, CycleOverview, EntryType,
|
CycleDataResponse, CycleOverview, EntryType,
|
||||||
FlowIntensity, HealthInsightResponse, MoodType,
|
FlowIntensity, HealthInsightResponse, MoodType,
|
||||||
CalendarEventCreate)
|
CalendarEventCreate)
|
||||||
|
from services.calendar_service.mobile_endpoint import MobileCalendarEntryCreate, mobile_create_calendar_entry
|
||||||
from shared.auth import get_current_user_from_token as get_current_user
|
from shared.auth import get_current_user_from_token as get_current_user
|
||||||
from shared.config import settings
|
from shared.config import settings
|
||||||
from shared.database import get_db
|
from shared.database import get_db
|
||||||
@@ -415,6 +416,160 @@ async def create_calendar_entry(
|
|||||||
|
|
||||||
return CalendarEntryResponse.model_validate(new_entry)
|
return CalendarEntryResponse.model_validate(new_entry)
|
||||||
|
|
||||||
|
# Мобильный эндпоинт для создания записей календаря
|
||||||
|
@app.post("/api/v1/calendar/entries/mobile", response_model=CalendarEntryResponse, status_code=201)
|
||||||
|
async def create_mobile_calendar_entry_endpoint(
|
||||||
|
mobile_entry: MobileCalendarEntryCreate,
|
||||||
|
current_user: Dict = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Создать новую запись календаря из мобильного приложения"""
|
||||||
|
import logging
|
||||||
|
logging.info(f"Получены данные мобильного приложения: {mobile_entry.model_dump()}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Преобразуем симптомы из списка в строку
|
||||||
|
symptoms_str = ""
|
||||||
|
if mobile_entry.symptoms:
|
||||||
|
symptoms_str = ", ".join(mobile_entry.symptoms)
|
||||||
|
logging.info(f"Преобразованы симптомы в строку: {symptoms_str}")
|
||||||
|
|
||||||
|
# Преобразуем данные из мобильного формата в формат сервера
|
||||||
|
server_entry = CalendarEntryCreate(
|
||||||
|
entry_date=mobile_entry.date,
|
||||||
|
entry_type=EntryType.PERIOD if mobile_entry.type == "MENSTRUATION" else
|
||||||
|
EntryType.OVULATION if mobile_entry.type == "OVULATION" else
|
||||||
|
EntryType.SYMPTOMS,
|
||||||
|
flow_intensity=FlowIntensity.from_int(mobile_entry.flow_intensity) if mobile_entry.flow_intensity else None,
|
||||||
|
mood=MoodType.from_mobile_mood(mobile_entry.mood) if mobile_entry.mood else None,
|
||||||
|
symptoms=symptoms_str,
|
||||||
|
notes=mobile_entry.notes,
|
||||||
|
# Значения по умолчанию для обязательных полей сервера
|
||||||
|
period_symptoms="",
|
||||||
|
energy_level=1, # Минимальное значение должно быть 1
|
||||||
|
sleep_hours=0,
|
||||||
|
medications="",
|
||||||
|
)
|
||||||
|
logging.info(f"Преобразовано в серверный формат: {server_entry}")
|
||||||
|
|
||||||
|
# Проверяем существование записи
|
||||||
|
existing = await db.execute(
|
||||||
|
select(CalendarEntry).filter(
|
||||||
|
and_(
|
||||||
|
CalendarEntry.user_id == current_user["user_id"],
|
||||||
|
CalendarEntry.entry_date == server_entry.entry_date,
|
||||||
|
CalendarEntry.entry_type == server_entry.entry_type.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
existing_entry = existing.scalars().first()
|
||||||
|
|
||||||
|
if existing_entry:
|
||||||
|
logging.info(f"Найдена существующая запись с ID: {existing_entry.id}")
|
||||||
|
# Если запись существует, обновляем её
|
||||||
|
if server_entry.flow_intensity is not None:
|
||||||
|
setattr(existing_entry, 'flow_intensity', server_entry.flow_intensity.value if server_entry.flow_intensity else None)
|
||||||
|
if server_entry.symptoms is not None:
|
||||||
|
setattr(existing_entry, 'symptoms', server_entry.symptoms)
|
||||||
|
if server_entry.mood is not None:
|
||||||
|
setattr(existing_entry, 'mood', server_entry.mood.value if server_entry.mood else None)
|
||||||
|
if server_entry.notes is not None:
|
||||||
|
setattr(existing_entry, 'notes', server_entry.notes)
|
||||||
|
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(existing_entry)
|
||||||
|
logging.info("Существующая запись обновлена")
|
||||||
|
|
||||||
|
return CalendarEntryResponse.model_validate(existing_entry)
|
||||||
|
|
||||||
|
# Создаем новую запись в календаре
|
||||||
|
new_entry = CalendarEntry(
|
||||||
|
user_id=current_user["user_id"],
|
||||||
|
entry_date=server_entry.entry_date,
|
||||||
|
entry_type=server_entry.entry_type.value,
|
||||||
|
flow_intensity=server_entry.flow_intensity.value if server_entry.flow_intensity else None,
|
||||||
|
period_symptoms=server_entry.period_symptoms,
|
||||||
|
mood=server_entry.mood.value if server_entry.mood else None,
|
||||||
|
energy_level=server_entry.energy_level,
|
||||||
|
sleep_hours=server_entry.sleep_hours,
|
||||||
|
symptoms=server_entry.symptoms,
|
||||||
|
medications=server_entry.medications,
|
||||||
|
notes=server_entry.notes,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(new_entry)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(new_entry)
|
||||||
|
logging.info(f"Создана новая запись с ID: {new_entry.id}")
|
||||||
|
|
||||||
|
# Если это запись о менструации, обновляем данные цикла
|
||||||
|
if server_entry.entry_type == EntryType.PERIOD:
|
||||||
|
await update_cycle_data(current_user["user_id"], server_entry.entry_date, db)
|
||||||
|
logging.info("Данные цикла обновлены")
|
||||||
|
|
||||||
|
return CalendarEntryResponse.model_validate(new_entry)
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Ошибка при создании записи календаря: {str(e)}")
|
||||||
|
await db.rollback() # Откатываем транзакцию при ошибке
|
||||||
|
raise HTTPException(status_code=500, detail=f"Ошибка сервера: {str(e)}")
|
||||||
|
|
||||||
|
@app.post("/debug/mobile-entry", status_code=200)
|
||||||
|
async def debug_mobile_calendar_entry(
|
||||||
|
mobile_entry: MobileCalendarEntryCreate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Тестовый эндпоинт для проверки преобразования данных из мобильного формата (без создания записи)"""
|
||||||
|
# Используем фиктивного пользователя для тестирования
|
||||||
|
mock_user = {"user_id": 1, "username": "test_user"}
|
||||||
|
|
||||||
|
# Преобразуем симптомы из списка в строку
|
||||||
|
symptoms_str = ""
|
||||||
|
if mobile_entry.symptoms:
|
||||||
|
symptoms_str = ", ".join(mobile_entry.symptoms)
|
||||||
|
|
||||||
|
# Преобразуем данные из мобильного формата в формат сервера
|
||||||
|
server_entry = CalendarEntryCreate(
|
||||||
|
entry_date=mobile_entry.date,
|
||||||
|
entry_type=EntryType.PERIOD if mobile_entry.type == "MENSTRUATION" else
|
||||||
|
EntryType.OVULATION if mobile_entry.type == "OVULATION" else
|
||||||
|
EntryType.SYMPTOMS,
|
||||||
|
flow_intensity=FlowIntensity.from_int(mobile_entry.flow_intensity) if mobile_entry.flow_intensity else None,
|
||||||
|
mood=MoodType.from_mobile_mood(mobile_entry.mood) if mobile_entry.mood else None,
|
||||||
|
symptoms=symptoms_str,
|
||||||
|
notes=mobile_entry.notes,
|
||||||
|
# Значения по умолчанию для обязательных полей сервера
|
||||||
|
period_symptoms="",
|
||||||
|
energy_level=1, # Минимальное значение должно быть 1
|
||||||
|
sleep_hours=0,
|
||||||
|
medications="",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем ответ без сохранения в БД
|
||||||
|
response_data = {
|
||||||
|
"id": 0, # Фиктивный ID
|
||||||
|
"user_id": mock_user["user_id"],
|
||||||
|
"entry_date": server_entry.entry_date.isoformat(),
|
||||||
|
"entry_type": server_entry.entry_type.value,
|
||||||
|
"flow_intensity": server_entry.flow_intensity.value if server_entry.flow_intensity else None,
|
||||||
|
"period_symptoms": server_entry.period_symptoms,
|
||||||
|
"mood": server_entry.mood.value if server_entry.mood else None,
|
||||||
|
"energy_level": server_entry.energy_level,
|
||||||
|
"sleep_hours": server_entry.sleep_hours,
|
||||||
|
"symptoms": server_entry.symptoms,
|
||||||
|
"medications": server_entry.medications,
|
||||||
|
"notes": server_entry.notes,
|
||||||
|
"created_at": date.today().isoformat(),
|
||||||
|
"updated_at": date.today().isoformat(),
|
||||||
|
"is_active": True
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
"message": "Данные успешно преобразованы из мобильного формата (без сохранения в БД)",
|
||||||
|
"original_data": mobile_entry.model_dump(),
|
||||||
|
"transformed_data": response_data
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/v1/entries/{entry_id}")
|
@app.delete("/api/v1/entries/{entry_id}")
|
||||||
async def delete_calendar_entry(
|
async def delete_calendar_entry(
|
||||||
@@ -476,14 +631,14 @@ async def create_mobile_calendar_entry(
|
|||||||
|
|
||||||
if existing_entry:
|
if existing_entry:
|
||||||
# Если запись существует, обновляем её
|
# Если запись существует, обновляем её
|
||||||
if server_entry_data.flow_intensity:
|
if server_entry_data.flow_intensity is not None:
|
||||||
existing_entry.flow_intensity = server_entry_data.flow_intensity.value
|
setattr(existing_entry, 'flow_intensity', server_entry_data.flow_intensity.value if server_entry_data.flow_intensity else None)
|
||||||
if server_entry_data.symptoms:
|
if server_entry_data.symptoms is not None:
|
||||||
existing_entry.symptoms = server_entry_data.symptoms
|
setattr(existing_entry, 'symptoms', server_entry_data.symptoms)
|
||||||
if server_entry_data.mood:
|
if server_entry_data.mood is not None:
|
||||||
existing_entry.mood = server_entry_data.mood.value
|
setattr(existing_entry, 'mood', server_entry_data.mood.value if server_entry_data.mood else None)
|
||||||
if server_entry_data.notes:
|
if server_entry_data.notes is not None:
|
||||||
existing_entry.notes = server_entry_data.notes
|
setattr(existing_entry, 'notes', server_entry_data.notes)
|
||||||
|
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(existing_entry)
|
await db.refresh(existing_entry)
|
||||||
|
|||||||
83
services/calendar_service/mobile_endpoint.py
Normal file
83
services/calendar_service/mobile_endpoint.py
Normal file
@@ -0,0 +1,83 @@
|
|||||||
|
from datetime import date
|
||||||
|
from enum import Enum
|
||||||
|
from typing import Optional, Dict, Any, List, Union
|
||||||
|
|
||||||
|
from fastapi import Depends, FastAPI, HTTPException
|
||||||
|
from pydantic import BaseModel, Field, root_validator
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
from .schemas import (
|
||||||
|
EntryType, FlowIntensity, MoodType, CalendarEntryCreate, CalendarEntryResponse
|
||||||
|
)
|
||||||
|
from .schemas_mobile import (
|
||||||
|
MobileFlowIntensity, MobileMood, convert_mobile_app_format_to_server
|
||||||
|
)
|
||||||
|
from services.calendar_service.models import CalendarEntry
|
||||||
|
from shared.auth import get_current_user_from_token as get_current_user
|
||||||
|
from shared.database import get_db
|
||||||
|
|
||||||
|
|
||||||
|
class MobileCalendarEntryCreate(BaseModel):
|
||||||
|
"""Схема для создания записей в календаре из мобильного приложения"""
|
||||||
|
date: date
|
||||||
|
type: str # Тип записи (MENSTRUATION, OVULATION и т.д.)
|
||||||
|
flow_intensity: Optional[int] = None # Шкала 1-5
|
||||||
|
symptoms: Optional[List[str]] = None # Массив строк с симптомами
|
||||||
|
mood: Optional[str] = None # Настроение как строка (NORMAL, HAPPY и т.д.)
|
||||||
|
notes: Optional[str] = None
|
||||||
|
|
||||||
|
|
||||||
|
async def mobile_create_calendar_entry(
|
||||||
|
mobile_entry: MobileCalendarEntryCreate,
|
||||||
|
current_user: Dict = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Создать новую запись в календаре из мобильного приложения"""
|
||||||
|
|
||||||
|
# Преобразуем симптомы из списка в строку
|
||||||
|
symptoms_str = ""
|
||||||
|
if mobile_entry.symptoms:
|
||||||
|
symptoms_str = ", ".join(mobile_entry.symptoms)
|
||||||
|
|
||||||
|
# Преобразуем формат мобильного приложения в формат сервера
|
||||||
|
server_entry = CalendarEntryCreate(
|
||||||
|
entry_date=mobile_entry.date,
|
||||||
|
entry_type=EntryType.PERIOD if mobile_entry.type == "MENSTRUATION" else
|
||||||
|
EntryType.OVULATION if mobile_entry.type == "OVULATION" else
|
||||||
|
EntryType.SYMPTOMS,
|
||||||
|
flow_intensity=FlowIntensity.from_int(mobile_entry.flow_intensity) if mobile_entry.flow_intensity else None,
|
||||||
|
mood=MoodType.from_mobile_mood(mobile_entry.mood) if mobile_entry.mood else None,
|
||||||
|
symptoms=symptoms_str,
|
||||||
|
notes=mobile_entry.notes,
|
||||||
|
# Значения по умолчанию для обязательных полей сервера
|
||||||
|
period_symptoms="",
|
||||||
|
energy_level=1, # Минимальное значение должно быть 1
|
||||||
|
sleep_hours=0,
|
||||||
|
medications="",
|
||||||
|
)
|
||||||
|
|
||||||
|
# Создаем новую запись в календаре
|
||||||
|
new_entry = CalendarEntry(
|
||||||
|
user_id=current_user["user_id"],
|
||||||
|
entry_date=server_entry.entry_date,
|
||||||
|
entry_type=server_entry.entry_type.value,
|
||||||
|
flow_intensity=server_entry.flow_intensity.value if server_entry.flow_intensity else None,
|
||||||
|
period_symptoms=server_entry.period_symptoms,
|
||||||
|
mood=server_entry.mood.value if server_entry.mood else None,
|
||||||
|
energy_level=server_entry.energy_level,
|
||||||
|
sleep_hours=server_entry.sleep_hours,
|
||||||
|
symptoms=server_entry.symptoms,
|
||||||
|
medications=server_entry.medications,
|
||||||
|
notes=server_entry.notes,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(new_entry)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(new_entry)
|
||||||
|
|
||||||
|
# Если это запись о менструации, обновляем данные цикла
|
||||||
|
if server_entry.entry_type == EntryType.PERIOD:
|
||||||
|
from .main import update_cycle_data
|
||||||
|
await update_cycle_data(current_user["user_id"], server_entry.entry_date, db)
|
||||||
|
|
||||||
|
return CalendarEntryResponse.model_validate(new_entry)
|
||||||
@@ -73,18 +73,38 @@ class MoodType(str, Enum):
|
|||||||
TIRED = "tired"
|
TIRED = "tired"
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def from_mobile_mood(cls, mood_type: 'MobileMoodType') -> Optional['MoodType']:
|
def from_mobile_mood(cls, mood_str: str) -> Optional['MoodType']:
|
||||||
"""Конвертировать из мобильного типа настроения"""
|
"""Конвертировать строку настроения из мобильного приложения"""
|
||||||
if mood_type == MobileMoodType.NORMAL:
|
if not mood_str:
|
||||||
return None
|
return None
|
||||||
mapping = {
|
|
||||||
MobileMoodType.HAPPY: cls.HAPPY,
|
# Преобразуем строку в enum или используем исходную строку, если не удалось
|
||||||
MobileMoodType.SAD: cls.SAD,
|
try:
|
||||||
MobileMoodType.ANXIOUS: cls.ANXIOUS,
|
mood_type = MobileMoodType(mood_str)
|
||||||
MobileMoodType.IRRITATED: cls.IRRITATED,
|
if mood_type == MobileMoodType.NORMAL:
|
||||||
MobileMoodType.STRESSED: cls.ANXIOUS,
|
return None
|
||||||
}
|
|
||||||
return mapping.get(mood_type)
|
mapping = {
|
||||||
|
MobileMoodType.HAPPY: cls.HAPPY,
|
||||||
|
MobileMoodType.SAD: cls.SAD,
|
||||||
|
MobileMoodType.ANXIOUS: cls.ANXIOUS,
|
||||||
|
MobileMoodType.IRRITATED: cls.IRRITATED,
|
||||||
|
MobileMoodType.STRESSED: cls.ANXIOUS,
|
||||||
|
}
|
||||||
|
return mapping.get(mood_type)
|
||||||
|
except ValueError:
|
||||||
|
# Если строка не является допустимым MobileMoodType
|
||||||
|
# Пробуем сопоставить с сервером напрямую
|
||||||
|
mood_map = {
|
||||||
|
"HAPPY": cls.HAPPY,
|
||||||
|
"SAD": cls.SAD,
|
||||||
|
"ANXIOUS": cls.ANXIOUS,
|
||||||
|
"IRRITATED": cls.IRRITATED,
|
||||||
|
"ENERGETIC": cls.ENERGETIC,
|
||||||
|
"TIRED": cls.TIRED,
|
||||||
|
"NORMAL": cls.HAPPY, # NORMAL мапится на HAPPY
|
||||||
|
}
|
||||||
|
return mood_map.get(mood_str)
|
||||||
|
|
||||||
|
|
||||||
class CalendarEntryBase(BaseModel):
|
class CalendarEntryBase(BaseModel):
|
||||||
|
|||||||
@@ -51,8 +51,9 @@ class MobileMood(str, Enum):
|
|||||||
"""Mood values used in the mobile app"""
|
"""Mood values used in the mobile app"""
|
||||||
HAPPY = "HAPPY"
|
HAPPY = "HAPPY"
|
||||||
SAD = "SAD"
|
SAD = "SAD"
|
||||||
|
NORMAL = "NORMAL" # Added for mobile app support
|
||||||
ANXIOUS = "ANXIOUS"
|
ANXIOUS = "ANXIOUS"
|
||||||
IRRITABLE = "IRRITABLE"
|
IRRITATED = "IRRITATED"
|
||||||
ENERGETIC = "ENERGETIC"
|
ENERGETIC = "ENERGETIC"
|
||||||
TIRED = "TIRED"
|
TIRED = "TIRED"
|
||||||
|
|
||||||
@@ -65,8 +66,9 @@ class MobileMood(str, Enum):
|
|||||||
mapping = {
|
mapping = {
|
||||||
cls.HAPPY: MoodType.HAPPY,
|
cls.HAPPY: MoodType.HAPPY,
|
||||||
cls.SAD: MoodType.SAD,
|
cls.SAD: MoodType.SAD,
|
||||||
|
cls.NORMAL: MoodType.HAPPY, # NORMAL maps to HAPPY
|
||||||
cls.ANXIOUS: MoodType.ANXIOUS,
|
cls.ANXIOUS: MoodType.ANXIOUS,
|
||||||
cls.IRRITABLE: MoodType.IRRITATED, # Different spelling
|
cls.IRRITATED: MoodType.IRRITATED, # Different spelling
|
||||||
cls.ENERGETIC: MoodType.ENERGETIC,
|
cls.ENERGETIC: MoodType.ENERGETIC,
|
||||||
cls.TIRED: MoodType.TIRED,
|
cls.TIRED: MoodType.TIRED,
|
||||||
}
|
}
|
||||||
@@ -195,3 +197,70 @@ def convert_server_response_to_mobile_app(
|
|||||||
created_at=getattr(server_response, "created_at", date.today()),
|
created_at=getattr(server_response, "created_at", date.today()),
|
||||||
updated_at=getattr(server_response, "created_at", date.today()), # Fallback to created_at
|
updated_at=getattr(server_response, "created_at", date.today()), # Fallback to created_at
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def convert_mobile_entry_type(mobile_type: str) -> str:
|
||||||
|
"""Конвертирует тип записи из мобильного формата в формат API"""
|
||||||
|
mapping = {
|
||||||
|
"MENSTRUATION": "period",
|
||||||
|
"OVULATION": "ovulation",
|
||||||
|
"FERTILE": "fertile_window",
|
||||||
|
"OTHER": "other"
|
||||||
|
}
|
||||||
|
return mapping.get(mobile_type, "other")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_mobile_flow_intensity(intensity: int) -> str:
|
||||||
|
"""Конвертирует интенсивность потока из мобильного формата в формат API"""
|
||||||
|
if intensity is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
mapping = {
|
||||||
|
1: "very_light",
|
||||||
|
2: "light",
|
||||||
|
3: "medium",
|
||||||
|
4: "heavy",
|
||||||
|
5: "very_heavy"
|
||||||
|
}
|
||||||
|
return mapping.get(intensity, "medium")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_mobile_mood(mood: str) -> str:
|
||||||
|
"""Конвертирует настроение из мобильного формата в формат API"""
|
||||||
|
if mood is None:
|
||||||
|
return None
|
||||||
|
|
||||||
|
mapping = {
|
||||||
|
"HAPPY": "happy",
|
||||||
|
"SAD": "sad",
|
||||||
|
"NORMAL": "happy", # NORMAL мапится на happy
|
||||||
|
"ANXIOUS": "anxious",
|
||||||
|
"IRRITATED": "irritated",
|
||||||
|
"ENERGETIC": "energetic",
|
||||||
|
"TIRED": "tired"
|
||||||
|
}
|
||||||
|
return mapping.get(mood, "happy")
|
||||||
|
|
||||||
|
|
||||||
|
def convert_mobile_symptoms(symptoms: List[str]) -> str:
|
||||||
|
"""Конвертирует список симптомов из мобильного формата в строку для API"""
|
||||||
|
if not symptoms:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
# Преобразуем все симптомы в нижний регистр и соединяем запятыми
|
||||||
|
return ",".join([symptom.lower() for symptom in symptoms])
|
||||||
|
|
||||||
|
|
||||||
|
def mobile_to_api_format(mobile_data: Dict[str, Any]) -> Dict[str, Any]:
|
||||||
|
"""Конвертирует данные из мобильного формата в формат API"""
|
||||||
|
api_data = {
|
||||||
|
"entry_date": mobile_data.get("date"),
|
||||||
|
"entry_type": convert_mobile_entry_type(mobile_data.get("type")),
|
||||||
|
"flow_intensity": convert_mobile_flow_intensity(mobile_data.get("flow_intensity")),
|
||||||
|
"mood": convert_mobile_mood(mobile_data.get("mood")),
|
||||||
|
"symptoms": convert_mobile_symptoms(mobile_data.get("symptoms")),
|
||||||
|
"notes": mobile_data.get("notes", "")
|
||||||
|
}
|
||||||
|
|
||||||
|
# Удаляем None значения
|
||||||
|
return {k: v for k, v in api_data.items() if v is not None}
|
||||||
|
|||||||
47
setup_mobile_test.py
Executable file
47
setup_mobile_test.py
Executable file
@@ -0,0 +1,47 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# Запрашиваем токен авторизации (предполагается, что он уже есть в системе)
|
||||||
|
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOSIsImVtYWlsIjoidGVzdDJAZXhhbXBsZS5jb20iLCJleHAiOjE3NTg4NjY5ODJ9._AXkBLeMI4zxC9shFUS3744miuyO8CDnJD1X1AqbLsw"
|
||||||
|
|
||||||
|
# Данные для мобильного запроса
|
||||||
|
mobile_data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Запись из мобильного приложения"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Заголовки с токеном авторизации
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {TOKEN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Сохраняем токен в файл для повторного использования
|
||||||
|
with open('auth_token.txt', 'w') as f:
|
||||||
|
f.write(TOKEN)
|
||||||
|
|
||||||
|
print(f"Токен сохранен в файл auth_token.txt")
|
||||||
|
print(f"Данные для запроса к мобильному API сохранены в переменной mobile_data")
|
||||||
|
print("\nПример использования:")
|
||||||
|
print('curl -v -X POST http://localhost:8004/api/v1/calendar/entries/mobile -H "Content-Type: application/json" -H "Authorization: Bearer $(cat auth_token.txt)" -d \'{"date": "2025-09-26", "type": "MENSTRUATION", "flow_intensity": 3, "symptoms": ["CRAMPS", "HEADACHE"], "mood": "NORMAL", "notes": "Тестовая запись"}\'')
|
||||||
|
|
||||||
|
# Сохраняем пример запроса в файл для удобства
|
||||||
|
with open('mobile_api_example.sh', 'w') as f:
|
||||||
|
f.write('#!/bin/bash\n\n')
|
||||||
|
f.write('# Пример запроса к мобильному API календарного сервиса\n')
|
||||||
|
f.write('curl -v -X POST http://localhost:8004/api/v1/calendar/entries/mobile \\\n')
|
||||||
|
f.write(' -H "Content-Type: application/json" \\\n')
|
||||||
|
f.write(' -H "Authorization: Bearer $(cat auth_token.txt)" \\\n')
|
||||||
|
f.write(' -d \'{"date": "2025-09-26", "type": "MENSTRUATION", "flow_intensity": 3, "symptoms": ["CRAMPS", "HEADACHE"], "mood": "NORMAL", "notes": "Тестовая запись"}\'\n')
|
||||||
|
|
||||||
|
print("\nПример запроса также сохранен в файле mobile_api_example.sh")
|
||||||
|
print("Можно выполнить: chmod +x mobile_api_example.sh && ./mobile_api_example.sh")
|
||||||
66
simple_test.py
Normal file
66
simple_test.py
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
import traceback
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# API Gateway endpoint
|
||||||
|
BASE_URL = "http://localhost:8004"
|
||||||
|
|
||||||
|
def main():
|
||||||
|
try:
|
||||||
|
print("Начинаем тест мобильного эндпоинта...")
|
||||||
|
|
||||||
|
# Проверка работоспособности сервиса
|
||||||
|
print("Проверяем работоспособность сервиса...")
|
||||||
|
health_response = requests.get(f"{BASE_URL}/health")
|
||||||
|
print(f"Ответ о статусе: {health_response.status_code}")
|
||||||
|
print(f"Тело ответа: {health_response.text}")
|
||||||
|
|
||||||
|
# Данные в формате мобильного приложения
|
||||||
|
mobile_data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3, # средний (1-5)
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"], # массив строк
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тестовая запись из мобильного приложения"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Отправляемые данные: {json.dumps(mobile_data, indent=2)}")
|
||||||
|
|
||||||
|
# Отправляем запрос к тестовому эндпоинту
|
||||||
|
print(f"Отправляем запрос на {BASE_URL}/debug/mobile-entry")
|
||||||
|
response = requests.post(
|
||||||
|
f"{BASE_URL}/debug/mobile-entry",
|
||||||
|
json=mobile_data
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Статус ответа: {response.status_code}")
|
||||||
|
print(f"Заголовки ответа: {response.headers}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response_json = response.json()
|
||||||
|
print(f"Тело ответа: {json.dumps(response_json, indent=2)}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Ответ не является JSON: {response.text}")
|
||||||
|
|
||||||
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
|
print("Тест успешно завершен!")
|
||||||
|
else:
|
||||||
|
print("Тест завершился с ошибкой.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print(f"Ошибка подключения к {BASE_URL}. Убедитесь, что сервис запущен.")
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Неожиданная ошибка: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
103
test_calendar_mobile.py
Executable file
103
test_calendar_mobile.py
Executable file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import requests
|
||||||
|
from datetime import date, datetime
|
||||||
|
import time
|
||||||
|
|
||||||
|
# Настройки для тестирования
|
||||||
|
BASE_URL = "http://localhost:8000" # URL API Gateway
|
||||||
|
TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOSIsImVtYWlsIjoidGVzdDJAZXhhbXBsZS5jb20iLCJleHAiOjE3NTg4NjY5ODJ9._AXkBLeMI4zxC9shFUS3744miuyO8CDnJD1X1AqbLsw"
|
||||||
|
|
||||||
|
# Функция для добавления записи в календарь через мобильный эндпоинт
|
||||||
|
def create_mobile_calendar_entry():
|
||||||
|
url = f"{BASE_URL}/api/v1/calendar/entries/mobile"
|
||||||
|
|
||||||
|
# Данные для создания записи в формате мобильного приложения
|
||||||
|
today_str = date.today().isoformat()
|
||||||
|
|
||||||
|
data = {
|
||||||
|
"date": today_str,
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3, # medium
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "HAPPY",
|
||||||
|
"notes": "Тестовая запись из мобильного приложения"
|
||||||
|
}
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {TOKEN}",
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"\n=== Тест мобильного эндпоинта ===")
|
||||||
|
print(f"Отправка запроса POST на {url}")
|
||||||
|
print(f"Данные: {json.dumps(data, indent=2, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
response = requests.post(url, json=data, headers=headers)
|
||||||
|
|
||||||
|
print(f"Код ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
print("✓ Запрос успешно выполнен")
|
||||||
|
print(f"Ответ: {json.dumps(response.json(), indent=2, ensure_ascii=False)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"✗ Ошибка при выполнении запроса: {response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Функция для получения записей календаря
|
||||||
|
def get_calendar_entries():
|
||||||
|
url = f"{BASE_URL}/api/v1/entries"
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Authorization": f"Bearer {TOKEN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"\n=== Получение записей календаря ===")
|
||||||
|
print(f"Отправка запроса GET на {url}")
|
||||||
|
|
||||||
|
response = requests.get(url, headers=headers)
|
||||||
|
|
||||||
|
print(f"Код ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print("✓ Запрос успешно выполнен")
|
||||||
|
entries = response.json()
|
||||||
|
print(f"Количество записей: {len(entries)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"✗ Ошибка при выполнении запроса: {response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Тестирование API календаря для мобильного приложения")
|
||||||
|
|
||||||
|
# Проверяем, что сервисы запущены
|
||||||
|
try:
|
||||||
|
health_check = requests.get(f"{BASE_URL}/api/v1/gateway/health")
|
||||||
|
if health_check.status_code != 200:
|
||||||
|
print("API Gateway недоступен. Убедитесь, что сервисы запущены.")
|
||||||
|
sys.exit(1)
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print("Не удалось подключиться к API Gateway. Убедитесь, что сервисы запущены.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
# Запускаем тесты
|
||||||
|
success = True
|
||||||
|
|
||||||
|
# Тест 1: Создание записи в календаре через мобильный эндпоинт
|
||||||
|
if not create_mobile_calendar_entry():
|
||||||
|
success = False
|
||||||
|
|
||||||
|
# Тест 2: Проверка получения записей
|
||||||
|
if not get_calendar_entries():
|
||||||
|
success = False
|
||||||
|
|
||||||
|
# Выводим общий результат
|
||||||
|
if success:
|
||||||
|
print("\n✅ Все тесты пройдены успешно!")
|
||||||
|
sys.exit(0)
|
||||||
|
else:
|
||||||
|
print("\n❌ Некоторые тесты не пройдены.")
|
||||||
|
sys.exit(1)
|
||||||
30
test_debug_endpoint.sh
Executable file
30
test_debug_endpoint.sh
Executable file
@@ -0,0 +1,30 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# Проверяем, запущен ли сервер календаря
|
||||||
|
if ! curl -s http://localhost:8004/health > /dev/null; then
|
||||||
|
echo "Сервер календаря не запущен. Запускаем..."
|
||||||
|
cd /home/trevor/dev/chat
|
||||||
|
source .venv/bin/activate
|
||||||
|
PYTHONPATH=/home/trevor/dev/chat python -m uvicorn services.calendar_service.main:app --host 0.0.0.0 --port 8004 &
|
||||||
|
sleep 5
|
||||||
|
echo "Сервер запущен в фоновом режиме"
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "Проверка работоспособности сервера..."
|
||||||
|
HEALTH_RESPONSE=$(curl -s http://localhost:8004/health)
|
||||||
|
echo "Ответ сервера: $HEALTH_RESPONSE"
|
||||||
|
|
||||||
|
echo -e "\n=== Тестирование отладочного эндпоинта (без аутентификации) ==="
|
||||||
|
curl -v -X POST http://localhost:8004/debug/mobile-entry \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"date": "2023-09-26",
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тестовая запись из скрипта"
|
||||||
|
}'
|
||||||
|
|
||||||
|
echo -e "\n\n=== Проверка созданных записей ==="
|
||||||
|
curl -s http://localhost:8004/debug/entries | head -n 20
|
||||||
121
test_mobile_api.py
Executable file
121
test_mobile_api.py
Executable file
@@ -0,0 +1,121 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# Используем сохраненный токен авторизации
|
||||||
|
with open('auth_token.txt', 'r') as f:
|
||||||
|
TOKEN = f.read().strip()
|
||||||
|
|
||||||
|
# Базовый URL для тестирования
|
||||||
|
BASE_URL = "http://localhost:8004"
|
||||||
|
HEADERS = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {TOKEN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Функция для тестирования мобильного эндпоинта
|
||||||
|
def test_mobile_endpoint():
|
||||||
|
url = f"{BASE_URL}/api/v1/calendar/entries/mobile"
|
||||||
|
|
||||||
|
# Данные для мобильного запроса
|
||||||
|
data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тест мобильного API"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n1. Тестирование создания записи через мобильный API...")
|
||||||
|
print(f"POST {url}")
|
||||||
|
print(f"Данные: {json.dumps(data, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=data, headers=HEADERS)
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
print(f"✅ Успешно! Статус: {response.status_code}")
|
||||||
|
print(f"Ответ: {json.dumps(response.json(), ensure_ascii=False, indent=2)}")
|
||||||
|
|
||||||
|
# Теперь проверим, что мы можем получить запись
|
||||||
|
entry_id = response.json().get("id")
|
||||||
|
return entry_id
|
||||||
|
else:
|
||||||
|
print(f"❌ Ошибка! Статус: {response.status_code}")
|
||||||
|
print(f"Ответ: {response.text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Исключение: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
# Функция для проверки получения всех записей
|
||||||
|
def test_get_entries(entry_id=None):
|
||||||
|
url = f"{BASE_URL}/api/v1/calendar/entries"
|
||||||
|
|
||||||
|
print("\n2. Проверка получения записей календаря...")
|
||||||
|
print(f"GET {url}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(url, headers=HEADERS)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"✅ Успешно! Статус: {response.status_code}")
|
||||||
|
entries = response.json()
|
||||||
|
print(f"Получено записей: {len(entries)}")
|
||||||
|
|
||||||
|
# Если у нас есть ID записи, которую мы создали ранее, проверим ее наличие
|
||||||
|
if entry_id:
|
||||||
|
found = False
|
||||||
|
for entry in entries:
|
||||||
|
if entry.get("id") == entry_id:
|
||||||
|
found = True
|
||||||
|
print(f"✅ Созданная запись найдена в списке (ID: {entry_id})")
|
||||||
|
break
|
||||||
|
|
||||||
|
if not found:
|
||||||
|
print(f"❌ Созданная запись не найдена в списке (ID: {entry_id})")
|
||||||
|
else:
|
||||||
|
print(f"❌ Ошибка! Статус: {response.status_code}")
|
||||||
|
print(f"Ответ: {response.text}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Исключение: {str(e)}")
|
||||||
|
|
||||||
|
# Тестирование отладочного эндпоинта (без аутентификации)
|
||||||
|
def test_debug_endpoint():
|
||||||
|
url = f"{BASE_URL}/debug/mobile-entry"
|
||||||
|
|
||||||
|
# Данные для мобильного запроса
|
||||||
|
data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тест отладочного API"
|
||||||
|
}
|
||||||
|
|
||||||
|
print("\n3. Тестирование отладочного эндпоинта...")
|
||||||
|
print(f"POST {url}")
|
||||||
|
print(f"Данные: {json.dumps(data, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"✅ Успешно! Статус: {response.status_code}")
|
||||||
|
print(f"Ответ: {json.dumps(response.json(), ensure_ascii=False, indent=2)}")
|
||||||
|
else:
|
||||||
|
print(f"❌ Ошибка! Статус: {response.status_code}")
|
||||||
|
print(f"Ответ: {response.text}")
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Исключение: {str(e)}")
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=== Тестирование мобильного API календарного сервиса ===")
|
||||||
|
entry_id = test_mobile_endpoint()
|
||||||
|
test_get_entries(entry_id)
|
||||||
|
test_debug_endpoint()
|
||||||
|
print("\nТестирование завершено!")
|
||||||
103
test_mobile_endpoint.py
Executable file
103
test_mobile_endpoint.py
Executable file
@@ -0,0 +1,103 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# API Gateway endpoint
|
||||||
|
BASE_URL = "http://localhost:8004"
|
||||||
|
|
||||||
|
# Имя пользователя и пароль для аутентификации
|
||||||
|
# (предполагается, что у вас есть настроенный доступ)
|
||||||
|
AUTH_DATA = {
|
||||||
|
"username": "test_user",
|
||||||
|
"password": "test_password"
|
||||||
|
}
|
||||||
|
|
||||||
|
# Получение токена аутентификации
|
||||||
|
def get_token():
|
||||||
|
# Обычно мы бы получили токен из сервиса аутентификации,
|
||||||
|
# но для теста будем использовать фиктивный токен
|
||||||
|
# или можем запросить его из user_service
|
||||||
|
return "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJ1c2VyX2lkIjoxLCJ1c2VybmFtZSI6InRlc3RfdXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsImV4cCI6MTY5ODI0OTc2Mn0.HS5-cPVZwv55h0CZO0q_Z_1vNY9w8B6YsPJytj4UI0A"
|
||||||
|
|
||||||
|
# Проверка работоспособности сервиса
|
||||||
|
def check_health():
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{BASE_URL}/health")
|
||||||
|
if response.status_code == 200:
|
||||||
|
print(f"Сервис календаря работает. Ответ: {response.json()}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"Сервис календаря доступен, но возвращает ошибку: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except requests.exceptions.ConnectionError:
|
||||||
|
print(f"Ошибка подключения к {BASE_URL}. Убедитесь, что сервис запущен.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Тестовые данные для мобильного приложения
|
||||||
|
def test_create_calendar_entry_mobile():
|
||||||
|
try:
|
||||||
|
token = get_token()
|
||||||
|
print(f"Токен получен: {token[:15]}...")
|
||||||
|
|
||||||
|
# Данные в формате мобильного приложения
|
||||||
|
mobile_data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3, # средний (1-5)
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"], # массив строк
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тестовая запись из мобильного приложения"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Отправляемые данные: {json.dumps(mobile_data, indent=2)}")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {token}"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Отправляем запрос на {BASE_URL}/debug/mobile-entry")
|
||||||
|
|
||||||
|
# Используем тестовый эндпоинт без аутентификации
|
||||||
|
print(f"Отправляем запрос на {BASE_URL}/debug/mobile-entry")
|
||||||
|
response = requests.post(
|
||||||
|
f"{BASE_URL}/debug/mobile-entry",
|
||||||
|
json=mobile_data
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Статус ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
|
print(f"Тело успешного ответа: {json.dumps(response.json(), indent=2)}")
|
||||||
|
return response.json()
|
||||||
|
else:
|
||||||
|
print("Ошибка при создании записи")
|
||||||
|
try:
|
||||||
|
print(f"Тело ответа с ошибкой: {json.dumps(response.json(), indent=2)}")
|
||||||
|
except json.JSONDecodeError:
|
||||||
|
print(f"Тело ответа не является JSON: {response.text}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
except requests.exceptions.RequestException as e:
|
||||||
|
print(f"Ошибка при выполнении запроса: {e}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Неожиданная ошибка: {e}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("Тестирование создания записи календаря из мобильного приложения...")
|
||||||
|
|
||||||
|
if not check_health():
|
||||||
|
print("Сервис недоступен. Тестирование прекращено.")
|
||||||
|
sys.exit(1)
|
||||||
|
|
||||||
|
result = test_create_calendar_entry_mobile()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print("Тест успешно завершен!")
|
||||||
|
else:
|
||||||
|
print("Тест завершился с ошибкой.")
|
||||||
|
sys.exit(1)
|
||||||
171
test_mobile_endpoints.py
Normal file
171
test_mobile_endpoints.py
Normal file
@@ -0,0 +1,171 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import requests
|
||||||
|
import sys
|
||||||
|
import traceback
|
||||||
|
from datetime import date
|
||||||
|
|
||||||
|
# API Gateway endpoint
|
||||||
|
BASE_URL = "http://localhost:8004"
|
||||||
|
|
||||||
|
# Токен для аутентификации - замените на действующий токен
|
||||||
|
AUTH_TOKEN = "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIyOSIsImVtYWlsIjoidGVzdDJAZXhhbXBsZS5jb20iLCJleHAiOjE3NTg4NjY5ODJ9._AXkBLeMI4zxC9shFUS3744miuyO8CDnJD1X1AqbLsw"
|
||||||
|
|
||||||
|
def test_health():
|
||||||
|
"""Проверка доступности сервиса"""
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{BASE_URL}/health")
|
||||||
|
print(f"Статус сервиса: {response.status_code}")
|
||||||
|
print(f"Ответ: {response.text}")
|
||||||
|
return response.status_code == 200
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при проверке сервиса: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_authenticated_endpoint():
|
||||||
|
"""Тестирование аутентифицированного эндпоинта для мобильного приложения"""
|
||||||
|
print("\n=== Тестирование аутентифицированного эндпоинта ===")
|
||||||
|
|
||||||
|
# Данные в формате мобильного приложения
|
||||||
|
mobile_data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3,
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"],
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Запись из мобильного приложения через аутентифицированный эндпоинт"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Отправляемые данные: {json.dumps(mobile_data, indent=2)}")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json",
|
||||||
|
"Authorization": f"Bearer {AUTH_TOKEN}"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
f"{BASE_URL}/api/v1/calendar/entries/mobile",
|
||||||
|
headers=headers,
|
||||||
|
json=mobile_data,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Статус ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
|
print(f"Тело успешного ответа: {json.dumps(response.json(), indent=2)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("Ошибка при создании записи через аутентифицированный эндпоинт")
|
||||||
|
try:
|
||||||
|
print(f"Тело ответа с ошибкой: {json.dumps(response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Тело ответа не является JSON: {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при выполнении запроса: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def test_debug_endpoint():
|
||||||
|
"""Тестирование отладочного эндпоинта для мобильного приложения (без аутентификации)"""
|
||||||
|
print("\n=== Тестирование отладочного эндпоинта (без аутентификации) ===")
|
||||||
|
|
||||||
|
# Данные в формате мобильного приложения
|
||||||
|
mobile_data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 4,
|
||||||
|
"symptoms": ["BACKACHE", "BLOATING"],
|
||||||
|
"mood": "HAPPY",
|
||||||
|
"notes": "Запись из мобильного приложения через отладочный эндпоинт"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Отправляемые данные: {json.dumps(mobile_data, indent=2)}")
|
||||||
|
|
||||||
|
headers = {
|
||||||
|
"Content-Type": "application/json"
|
||||||
|
}
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(
|
||||||
|
f"{BASE_URL}/debug/mobile-entry",
|
||||||
|
headers=headers,
|
||||||
|
json=mobile_data,
|
||||||
|
timeout=10
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Статус ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
|
print(f"Тело успешного ответа: {json.dumps(response.json(), indent=2)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("Ошибка при создании записи через отладочный эндпоинт")
|
||||||
|
try:
|
||||||
|
print(f"Тело ответа с ошибкой: {json.dumps(response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Тело ответа не является JSON: {response.text}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при выполнении запроса: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def verify_entries_created():
|
||||||
|
"""Проверка, что записи были созданы в БД"""
|
||||||
|
print("\n=== Проверка созданных записей ===")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.get(f"{BASE_URL}/debug/entries")
|
||||||
|
|
||||||
|
print(f"Статус ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
entries = response.json()
|
||||||
|
print(f"Количество записей в БД: {len(entries)}")
|
||||||
|
print("Последние 2 записи:")
|
||||||
|
for entry in entries[-2:]:
|
||||||
|
print(json.dumps(entry, indent=2))
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print(f"Ошибка при получении записей: {response.status_code}")
|
||||||
|
return False
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при проверке записей: {e}")
|
||||||
|
traceback.print_exc()
|
||||||
|
return False
|
||||||
|
|
||||||
|
def main():
|
||||||
|
print("=== Тестирование мобильных эндпоинтов календарного сервиса ===")
|
||||||
|
|
||||||
|
if not test_health():
|
||||||
|
print("Сервис недоступен. Завершение тестирования.")
|
||||||
|
return 1
|
||||||
|
|
||||||
|
debug_result = test_debug_endpoint()
|
||||||
|
auth_result = test_authenticated_endpoint()
|
||||||
|
|
||||||
|
if debug_result and auth_result:
|
||||||
|
print("\nВсе тесты успешно пройдены!")
|
||||||
|
verify_entries_created()
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print("\nНекоторые тесты не пройдены.")
|
||||||
|
if debug_result:
|
||||||
|
print("✓ Отладочный эндпоинт работает")
|
||||||
|
else:
|
||||||
|
print("✗ Отладочный эндпоинт не работает")
|
||||||
|
|
||||||
|
if auth_result:
|
||||||
|
print("✓ Аутентифицированный эндпоинт работает")
|
||||||
|
else:
|
||||||
|
print("✗ Аутентифицированный эндпоинт не работает")
|
||||||
|
|
||||||
|
verify_entries_created()
|
||||||
|
return 1
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
sys.exit(main())
|
||||||
184
test_standalone.py
Normal file
184
test_standalone.py
Normal file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import json
|
||||||
|
import sys
|
||||||
|
import asyncio
|
||||||
|
import uvicorn
|
||||||
|
from fastapi import FastAPI, Depends, HTTPException
|
||||||
|
from datetime import date
|
||||||
|
from typing import Dict, Optional, List
|
||||||
|
from pydantic import BaseModel
|
||||||
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
|
# Импортируем необходимые модули из календарного сервиса
|
||||||
|
from services.calendar_service.schemas import (
|
||||||
|
EntryType, FlowIntensity, MoodType, CalendarEntryCreate, CalendarEntryResponse
|
||||||
|
)
|
||||||
|
from services.calendar_service.mobile_endpoint import MobileCalendarEntryCreate
|
||||||
|
from shared.database import get_db
|
||||||
|
|
||||||
|
# Создаем тестовое приложение
|
||||||
|
app = FastAPI(title="Test Mobile Endpoint")
|
||||||
|
|
||||||
|
@app.get("/health")
|
||||||
|
async def health_check():
|
||||||
|
"""Health check endpoint"""
|
||||||
|
return {"status": "healthy", "service": "test_mobile_endpoint"}
|
||||||
|
|
||||||
|
@app.post("/debug/mobile-entry", status_code=201)
|
||||||
|
async def test_mobile_endpoint(
|
||||||
|
mobile_entry: MobileCalendarEntryCreate,
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Test mobile endpoint without authentication"""
|
||||||
|
|
||||||
|
# Преобразуем симптомы из списка в строку
|
||||||
|
symptoms_str = ""
|
||||||
|
if mobile_entry.symptoms:
|
||||||
|
symptoms_str = ", ".join(mobile_entry.symptoms)
|
||||||
|
|
||||||
|
# Преобразуем данные из мобильного формата в формат сервера
|
||||||
|
entry_type = EntryType.PERIOD
|
||||||
|
if mobile_entry.type == "MENSTRUATION":
|
||||||
|
entry_type = EntryType.PERIOD
|
||||||
|
elif mobile_entry.type == "OVULATION":
|
||||||
|
entry_type = EntryType.OVULATION
|
||||||
|
else:
|
||||||
|
entry_type = EntryType.SYMPTOMS
|
||||||
|
|
||||||
|
# Преобразуем интенсивность потока
|
||||||
|
flow_intensity = None
|
||||||
|
if mobile_entry.flow_intensity is not None:
|
||||||
|
if mobile_entry.flow_intensity <= 1:
|
||||||
|
flow_intensity = FlowIntensity.SPOTTING
|
||||||
|
elif mobile_entry.flow_intensity <= 2:
|
||||||
|
flow_intensity = FlowIntensity.LIGHT
|
||||||
|
elif mobile_entry.flow_intensity <= 4:
|
||||||
|
flow_intensity = FlowIntensity.MEDIUM
|
||||||
|
else:
|
||||||
|
flow_intensity = FlowIntensity.HEAVY
|
||||||
|
|
||||||
|
# Преобразуем настроение
|
||||||
|
mood = None
|
||||||
|
if mobile_entry.mood:
|
||||||
|
if mobile_entry.mood == "HAPPY":
|
||||||
|
mood = MoodType.HAPPY
|
||||||
|
elif mobile_entry.mood == "SAD":
|
||||||
|
mood = MoodType.SAD
|
||||||
|
elif mobile_entry.mood == "NORMAL":
|
||||||
|
mood = MoodType.HAPPY # NORMAL мапится на HAPPY
|
||||||
|
elif mobile_entry.mood == "ANXIOUS":
|
||||||
|
mood = MoodType.ANXIOUS
|
||||||
|
elif mobile_entry.mood == "IRRITATED":
|
||||||
|
mood = MoodType.IRRITATED
|
||||||
|
elif mobile_entry.mood == "ENERGETIC":
|
||||||
|
mood = MoodType.ENERGETIC
|
||||||
|
elif mobile_entry.mood == "TIRED":
|
||||||
|
mood = MoodType.TIRED
|
||||||
|
|
||||||
|
# Создаем объект CalendarEntryResponse для возврата
|
||||||
|
response = {
|
||||||
|
"id": 1,
|
||||||
|
"user_id": 1,
|
||||||
|
"entry_date": mobile_entry.date.isoformat(),
|
||||||
|
"entry_type": entry_type.value,
|
||||||
|
"flow_intensity": flow_intensity.value if flow_intensity else None,
|
||||||
|
"period_symptoms": "",
|
||||||
|
"mood": mood.value if mood else None,
|
||||||
|
"energy_level": 1, # Минимальное значение должно быть 1
|
||||||
|
"sleep_hours": 0,
|
||||||
|
"symptoms": symptoms_str,
|
||||||
|
"medications": "",
|
||||||
|
"notes": mobile_entry.notes or "",
|
||||||
|
"created_at": date.today().isoformat(),
|
||||||
|
"updated_at": date.today().isoformat(),
|
||||||
|
}
|
||||||
|
|
||||||
|
return response
|
||||||
|
|
||||||
|
# Функция для отправки тестового запроса
|
||||||
|
async def send_test_request():
|
||||||
|
"""Отправка тестового запроса к эндпоинту"""
|
||||||
|
import httpx
|
||||||
|
|
||||||
|
# Данные в формате мобильного приложения
|
||||||
|
mobile_data = {
|
||||||
|
"date": date.today().isoformat(),
|
||||||
|
"type": "MENSTRUATION",
|
||||||
|
"flow_intensity": 3, # средний (1-5)
|
||||||
|
"symptoms": ["CRAMPS", "HEADACHE"], # массив строк
|
||||||
|
"mood": "NORMAL",
|
||||||
|
"notes": "Тестовая запись из мобильного приложения"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Отправляемые данные: {json.dumps(mobile_data, indent=2)}")
|
||||||
|
|
||||||
|
# Подождем, пока сервис запустится
|
||||||
|
await asyncio.sleep(2)
|
||||||
|
|
||||||
|
async with httpx.AsyncClient() as client:
|
||||||
|
try:
|
||||||
|
# Проверяем работоспособность сервиса
|
||||||
|
health_response = await client.get("http://localhost:8080/health")
|
||||||
|
print(f"Проверка работоспособности: {health_response.status_code}")
|
||||||
|
print(f"Ответ сервиса: {health_response.text}")
|
||||||
|
|
||||||
|
# Отправляем запрос к тестовому эндпоинту
|
||||||
|
response = await client.post(
|
||||||
|
"http://localhost:8080/debug/mobile-entry",
|
||||||
|
json=mobile_data
|
||||||
|
)
|
||||||
|
|
||||||
|
print(f"Статус ответа: {response.status_code}")
|
||||||
|
|
||||||
|
if response.status_code >= 200 and response.status_code < 300:
|
||||||
|
print(f"Тело успешного ответа: {json.dumps(response.json(), indent=2)}")
|
||||||
|
return True
|
||||||
|
else:
|
||||||
|
print("Ошибка при создании записи")
|
||||||
|
try:
|
||||||
|
print(f"Тело ответа с ошибкой: {json.dumps(response.json(), indent=2)}")
|
||||||
|
except:
|
||||||
|
print(f"Тело ответа не является JSON: {response.text}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
except Exception as e:
|
||||||
|
print(f"Ошибка при выполнении запроса: {e}")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# Основная функция
|
||||||
|
async def main():
|
||||||
|
# Запускаем сервер в отдельном процессе
|
||||||
|
config = uvicorn.Config(app, host="0.0.0.0", port=8080, log_level="info")
|
||||||
|
server = uvicorn.Server(config)
|
||||||
|
|
||||||
|
# Создаем задачу для запуска сервера
|
||||||
|
server_task = asyncio.create_task(server.serve())
|
||||||
|
|
||||||
|
# Дадим серверу немного времени на запуск
|
||||||
|
await asyncio.sleep(1)
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Отправляем тестовый запрос
|
||||||
|
result = await send_test_request()
|
||||||
|
|
||||||
|
if result:
|
||||||
|
print("Тест успешно завершен!")
|
||||||
|
return 0
|
||||||
|
else:
|
||||||
|
print("Тест завершился с ошибкой.")
|
||||||
|
return 1
|
||||||
|
finally:
|
||||||
|
# Останавливаем сервер
|
||||||
|
server.should_exit = True
|
||||||
|
await server_task
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
# Запускаем асинхронную функцию main()
|
||||||
|
import asyncio
|
||||||
|
try:
|
||||||
|
exit_code = asyncio.run(main())
|
||||||
|
sys.exit(exit_code)
|
||||||
|
except KeyboardInterrupt:
|
||||||
|
print("Выполнение прервано")
|
||||||
|
sys.exit(1)
|
||||||
83
update_token.py
Executable file
83
update_token.py
Executable file
@@ -0,0 +1,83 @@
|
|||||||
|
#!/usr/bin/env python
|
||||||
|
|
||||||
|
import requests
|
||||||
|
import json
|
||||||
|
|
||||||
|
# Базовый URL для тестирования
|
||||||
|
BASE_URL = "http://localhost:8001" # User service
|
||||||
|
|
||||||
|
def register_user():
|
||||||
|
"""Регистрация нового пользователя"""
|
||||||
|
url = f"{BASE_URL}/api/v1/auth/register"
|
||||||
|
data = {
|
||||||
|
"email": "test_mobile@example.com",
|
||||||
|
"password": "password123",
|
||||||
|
"first_name": "Test",
|
||||||
|
"last_name": "Mobile"
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Регистрация пользователя: {json.dumps(data, ensure_ascii=False)}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, json=data)
|
||||||
|
|
||||||
|
if response.status_code == 201:
|
||||||
|
print("✅ Пользователь успешно зарегистрирован")
|
||||||
|
user_data = response.json()
|
||||||
|
print(f"ID пользователя: {user_data.get('id')}")
|
||||||
|
return user_data
|
||||||
|
else:
|
||||||
|
print(f"❌ Ошибка при регистрации: {response.status_code}")
|
||||||
|
print(f"Ответ: {response.text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Исключение: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
def login_user(email, password):
|
||||||
|
"""Вход пользователя и получение токена"""
|
||||||
|
url = f"{BASE_URL}/api/v1/auth/token"
|
||||||
|
data = {
|
||||||
|
"username": email,
|
||||||
|
"password": password
|
||||||
|
}
|
||||||
|
|
||||||
|
print(f"Вход пользователя: {email}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
response = requests.post(url, data=data)
|
||||||
|
|
||||||
|
if response.status_code == 200:
|
||||||
|
token_data = response.json()
|
||||||
|
print("✅ Успешный вход")
|
||||||
|
|
||||||
|
# Сохраняем токен
|
||||||
|
token = token_data.get("access_token")
|
||||||
|
with open("auth_token.txt", "w") as f:
|
||||||
|
f.write(token)
|
||||||
|
|
||||||
|
print(f"✅ Токен сохранен в файл auth_token.txt")
|
||||||
|
return token
|
||||||
|
else:
|
||||||
|
print(f"❌ Ошибка при входе: {response.status_code}")
|
||||||
|
print(f"Ответ: {response.text}")
|
||||||
|
return None
|
||||||
|
except Exception as e:
|
||||||
|
print(f"❌ Исключение: {str(e)}")
|
||||||
|
return None
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
print("=== Обновление токена авторизации ===")
|
||||||
|
|
||||||
|
# Пробуем выполнить вход с существующими данными
|
||||||
|
token = login_user("test_mobile@example.com", "password123")
|
||||||
|
|
||||||
|
if not token:
|
||||||
|
# Если не удалось войти, регистрируем нового пользователя
|
||||||
|
user = register_user()
|
||||||
|
if user:
|
||||||
|
token = login_user("test_mobile@example.com", "password123")
|
||||||
|
|
||||||
|
print("\nПроцесс обновления токена завершен!")
|
||||||
|
if token:
|
||||||
|
print("Теперь вы можете использовать скрипт test_mobile_api.py для тестирования API")
|
||||||
Reference in New Issue
Block a user