This commit is contained in:
@@ -13,6 +13,7 @@ from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntr
|
||||
CycleDataResponse, CycleOverview, EntryType,
|
||||
FlowIntensity, HealthInsightResponse, MoodType,
|
||||
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.config import settings
|
||||
from shared.database import get_db
|
||||
@@ -415,6 +416,160 @@ async def create_calendar_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}")
|
||||
async def delete_calendar_entry(
|
||||
@@ -476,14 +631,14 @@ async def create_mobile_calendar_entry(
|
||||
|
||||
if existing_entry:
|
||||
# Если запись существует, обновляем её
|
||||
if server_entry_data.flow_intensity:
|
||||
existing_entry.flow_intensity = server_entry_data.flow_intensity.value
|
||||
if server_entry_data.symptoms:
|
||||
existing_entry.symptoms = server_entry_data.symptoms
|
||||
if server_entry_data.mood:
|
||||
existing_entry.mood = server_entry_data.mood.value
|
||||
if server_entry_data.notes:
|
||||
existing_entry.notes = server_entry_data.notes
|
||||
if server_entry_data.flow_intensity is not None:
|
||||
setattr(existing_entry, 'flow_intensity', server_entry_data.flow_intensity.value if server_entry_data.flow_intensity else None)
|
||||
if server_entry_data.symptoms is not None:
|
||||
setattr(existing_entry, 'symptoms', server_entry_data.symptoms)
|
||||
if server_entry_data.mood is not None:
|
||||
setattr(existing_entry, 'mood', server_entry_data.mood.value if server_entry_data.mood else None)
|
||||
if server_entry_data.notes is not None:
|
||||
setattr(existing_entry, 'notes', server_entry_data.notes)
|
||||
|
||||
await db.commit()
|
||||
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"
|
||||
|
||||
@classmethod
|
||||
def from_mobile_mood(cls, mood_type: 'MobileMoodType') -> Optional['MoodType']:
|
||||
"""Конвертировать из мобильного типа настроения"""
|
||||
if mood_type == MobileMoodType.NORMAL:
|
||||
def from_mobile_mood(cls, mood_str: str) -> Optional['MoodType']:
|
||||
"""Конвертировать строку настроения из мобильного приложения"""
|
||||
if not mood_str:
|
||||
return None
|
||||
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)
|
||||
|
||||
# Преобразуем строку в enum или используем исходную строку, если не удалось
|
||||
try:
|
||||
mood_type = MobileMoodType(mood_str)
|
||||
if mood_type == MobileMoodType.NORMAL:
|
||||
return None
|
||||
|
||||
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):
|
||||
|
||||
@@ -51,8 +51,9 @@ class MobileMood(str, Enum):
|
||||
"""Mood values used in the mobile app"""
|
||||
HAPPY = "HAPPY"
|
||||
SAD = "SAD"
|
||||
NORMAL = "NORMAL" # Added for mobile app support
|
||||
ANXIOUS = "ANXIOUS"
|
||||
IRRITABLE = "IRRITABLE"
|
||||
IRRITATED = "IRRITATED"
|
||||
ENERGETIC = "ENERGETIC"
|
||||
TIRED = "TIRED"
|
||||
|
||||
@@ -65,8 +66,9 @@ class MobileMood(str, Enum):
|
||||
mapping = {
|
||||
cls.HAPPY: MoodType.HAPPY,
|
||||
cls.SAD: MoodType.SAD,
|
||||
cls.NORMAL: MoodType.HAPPY, # NORMAL maps to HAPPY
|
||||
cls.ANXIOUS: MoodType.ANXIOUS,
|
||||
cls.IRRITABLE: MoodType.IRRITATED, # Different spelling
|
||||
cls.IRRITATED: MoodType.IRRITATED, # Different spelling
|
||||
cls.ENERGETIC: MoodType.ENERGETIC,
|
||||
cls.TIRED: MoodType.TIRED,
|
||||
}
|
||||
@@ -194,4 +196,71 @@ def convert_server_response_to_mobile_app(
|
||||
notes=server_response.notes,
|
||||
created_at=getattr(server_response, "created_at", date.today()),
|
||||
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}
|
||||
|
||||
Reference in New Issue
Block a user