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

This commit is contained in:
2025-10-07 16:25:52 +09:00
parent 76d0d86211
commit 91c7e04474
1171 changed files with 81940 additions and 44117 deletions

View File

@@ -1,10 +1,10 @@
from datetime import date, datetime, timedelta
from typing import Dict, List, Optional
from typing import Dict, List, Optional, Any
from fastapi import Depends, FastAPI, HTTPException, Query
from fastapi.middleware.cors import CORSMiddleware
from pydantic import BaseModel
from sqlalchemy import and_, desc, select
from sqlalchemy import and_, desc, select, func
from sqlalchemy.ext.asyncio import AsyncSession
from services.calendar_service.models import CalendarEntry, CycleData, HealthInsights
@@ -14,6 +14,10 @@ from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntr
FlowIntensity, HealthInsightResponse, MoodType,
CalendarEventCreate)
from services.calendar_service.mobile_endpoint import MobileCalendarEntryCreate, mobile_create_calendar_entry
from services.calendar_service.mobile_responses import (MobileCalendarEntryResponse,
MobileCalendarPeriodInfo,
MobilePredictionInfo,
MobileCalendarResponse)
from shared.auth import get_current_user_from_token as get_current_user
from shared.config import settings
from shared.database import get_db
@@ -349,7 +353,7 @@ async def get_all_calendar_entries(
if end_date:
query = query.filter(CalendarEntry.entry_date <= end_date)
if entry_type:
query = query.filter(CalendarEntry.entry_type == entry_type)
query = query.filter(CalendarEntry.entry_type == entry_type.value)
query = query.order_by(CalendarEntry.entry_date.desc()).limit(limit)
@@ -679,7 +683,191 @@ async def create_mobile_calendar_entry(
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
from .utils import safe_int, safe_str, safe_bool, safe_date, safe_datetime, safe_get_column_value
# Новый эндпоинт для мобильного приложения для получения записей календаря с фильтрацией по датам
@app.get("/api/v1/calendar/entries/mobile", response_model=MobileCalendarResponse)
async def get_mobile_calendar_entries(
start_date: Optional[date] = Query(None, description="Начальная дата для фильтрации (включительно)"),
end_date: Optional[date] = Query(None, description="Конечная дата для фильтрации (включительно)"),
entry_type: Optional[str] = Query(None, description="Тип записи (MENSTRUATION, OVULATION и т.д.)"),
current_user: Dict = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
limit: int = Query(100, ge=1, le=500, description="Максимальное количество записей"),
):
"""Получить записи календаря для мобильного приложения с фильтрацией по датам"""
import logging
logging.info(f"Запрос мобильных данных. start_date={start_date}, end_date={end_date}, entry_type={entry_type}")
try:
# Получаем записи из базы данных
query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user["user_id"])
# Фильтрация по датам
if start_date:
query = query.filter(CalendarEntry.entry_date >= start_date)
if end_date:
query = query.filter(CalendarEntry.entry_date <= end_date)
# Фильтрация по типу записи
if entry_type:
# Преобразуем тип из мобильного формата в серверный
server_entry_type = None
if entry_type == "MENSTRUATION":
server_entry_type = EntryType.PERIOD.value
elif entry_type == "OVULATION":
server_entry_type = EntryType.OVULATION.value
elif entry_type == "SYMPTOMS":
server_entry_type = EntryType.SYMPTOMS.value
if server_entry_type:
query = query.filter(CalendarEntry.entry_type == server_entry_type)
# Сортировка и ограничение количества записей
query = query.order_by(desc(CalendarEntry.entry_date)).limit(limit)
# Выполнение запроса
result = await db.execute(query)
entries = result.scalars().all()
# Преобразуем записи в формат мобильного приложения
mobile_entries = []
for entry in entries:
# Преобразуем тип записи из серверного формата в мобильный
mobile_type = "SYMPTOMS" # По умолчанию
entry_type_value = safe_str(entry.entry_type, "")
if entry_type_value == EntryType.PERIOD.value:
mobile_type = "MENSTRUATION"
elif entry_type_value == EntryType.OVULATION.value:
mobile_type = "OVULATION"
# Преобразуем симптомы из строки в список
symptoms_list = []
entry_symptoms = safe_str(entry.symptoms, "")
if entry_symptoms:
symptoms_list = [s.strip() for s in entry_symptoms.split(",")]
# Преобразуем flow_intensity, если есть
flow_intensity_value = None
entry_flow = safe_str(entry.flow_intensity, "")
if entry_flow in ['1', '2', '3', '4', '5']:
flow_intensity_value = int(entry_flow)
# Преобразуем mood, если есть
mood_value = None
entry_mood = safe_str(entry.mood, "")
if entry_mood:
mood_value = entry_mood.upper()
# Преобразуем notes, если есть
notes_value = safe_str(entry.notes, "")
# Получаем created_at
created_at_value = safe_datetime(
getattr(entry, 'created_at', None),
datetime.now()
).isoformat()
# Получаем is_predicted
is_predicted_value = safe_bool(
getattr(entry, 'is_predicted', None),
False
)
# Создаем мобильную запись
mobile_entry = MobileCalendarEntryResponse(
id=safe_int(entry.id),
uuid=str(safe_int(entry.id)), # Используем ID как UUID
date=safe_date(entry.entry_date, date.today()).isoformat(),
type=mobile_type,
flow_intensity=flow_intensity_value,
mood=mood_value,
symptoms=symptoms_list,
notes=notes_value,
created_at=created_at_value,
is_predicted=is_predicted_value
)
mobile_entries.append(mobile_entry)
# Получаем информацию о текущем цикле
current_cycle = await db.execute(
select(CycleData)
.filter(CycleData.user_id == current_user["user_id"])
.order_by(desc(CycleData.cycle_start_date))
.limit(1)
)
cycle_data = current_cycle.scalars().first()
# Создаем информацию о периоде
period_info = MobileCalendarPeriodInfo()
prediction_info = MobilePredictionInfo()
if cycle_data:
# Заполняем информацию о текущем цикле
cycle_start_date = safe_date(cycle_data.cycle_start_date)
if cycle_start_date:
period_info.current_cycle_start = cycle_start_date.isoformat()
cycle_length = safe_int(cycle_data.cycle_length)
if cycle_length > 0:
period_info.cycle_length = cycle_length
avg_cycle_length = safe_int(cycle_data.avg_cycle_length)
if avg_cycle_length > 0:
period_info.average_cycle_length = avg_cycle_length
# Если есть информация о периоде, рассчитываем дату окончания
period_length = safe_int(cycle_data.period_length)
if period_length > 0 and cycle_start_date:
end_date_value = cycle_start_date + timedelta(days=period_length)
period_info.expected_period_end = end_date_value.isoformat()
# Заполняем информацию о фертильном окне и овуляции
if cycle_start_date:
ovulation_day = avg_cycle_length // 2
ovulation_date = cycle_start_date + timedelta(days=ovulation_day)
period_info.ovulation_date = ovulation_date.isoformat()
# Фертильное окно начинается за 5 дней до овуляции и заканчивается через 1 день после
period_info.fertility_window_start = (ovulation_date - timedelta(days=5)).isoformat()
period_info.fertility_window_end = (ovulation_date + timedelta(days=1)).isoformat()
# Заполняем прогноз
next_period = safe_date(cycle_data.next_period_predicted)
if next_period:
prediction_info.next_period_date = next_period.isoformat()
prediction_info.confidence_level = 80 # Приблизительное значение уверенности
# Рассчитываем следующее фертильное окно и овуляцию
if avg_cycle_length > 0:
next_ovulation = next_period - timedelta(days=avg_cycle_length // 2)
prediction_info.next_ovulation_date = next_ovulation.isoformat()
prediction_info.next_fertile_window_start = (next_ovulation - timedelta(days=5)).isoformat()
prediction_info.next_fertile_window_end = (next_ovulation + timedelta(days=1)).isoformat()
# Собираем полный ответ
response = MobileCalendarResponse(
entries=mobile_entries,
period_info=period_info,
prediction=prediction_info
)
return response
except Exception as e:
logging.error(f"Ошибка при получении записей календаря: {str(e)}")
raise HTTPException(status_code=500, detail=f"Ошибка сервера: {str(e)}")
if __name__ == "__main__":
import uvicorn
from .mobile_responses import (
MobileCalendarEntryResponse,
MobileCalendarPeriodInfo,
MobilePredictionInfo,
MobileCalendarResponse
)
from .schemas_mobile import MobileFlowIntensity, MobileMood
uvicorn.run(app, host="0.0.0.0", port=8004)