This commit is contained in:
361
services/calendar_service/main.py.updated
Normal file
361
services/calendar_service/main.py.updated
Normal file
@@ -0,0 +1,361 @@
|
||||
"""
|
||||
Служба календаря для приложения женской безопасности.
|
||||
Предоставляет API для работы с записями календаря здоровья.
|
||||
|
||||
Поддерживаются следующие форматы запросов:
|
||||
1. Стандартный формат: /api/v1/calendar/entries
|
||||
2. Мобильный формат: /api/v1/calendar/entry
|
||||
3. Запросы без префикса /calendar: /api/v1/entries и /api/v1/entry
|
||||
"""
|
||||
|
||||
from fastapi import FastAPI, Depends, HTTPException, Header, Body, Query, Path
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from fastapi.responses import JSONResponse
|
||||
import json
|
||||
from typing import List, Optional, Dict, Any, Union
|
||||
from datetime import date, datetime, timedelta
|
||||
from pydantic import BaseModel, Field
|
||||
from enum import Enum
|
||||
import os
|
||||
import sys
|
||||
import uuid
|
||||
import logging
|
||||
from typing import Dict, List, Optional
|
||||
import time
|
||||
import asyncio
|
||||
|
||||
# Добавляем путь к общим модулям
|
||||
sys.path.insert(0, os.path.abspath(os.path.join(os.path.dirname(__file__), '../../')))
|
||||
|
||||
# Импортируем общие модули
|
||||
from shared.auth import get_current_user
|
||||
from shared.database import get_db
|
||||
from shared.config import settings
|
||||
from services.calendar_service.models import CalendarEntryInDB, EntryType, FlowIntensity, MoodType
|
||||
|
||||
# Схемы данных для календарных записей
|
||||
class CalendarEntryBase(BaseModel):
|
||||
entry_date: date
|
||||
entry_type: str
|
||||
flow_intensity: Optional[str] = None
|
||||
period_symptoms: Optional[str] = None
|
||||
mood: Optional[str] = None
|
||||
energy_level: Optional[int] = None
|
||||
sleep_hours: Optional[float] = None
|
||||
symptoms: Optional[str] = None
|
||||
medications: Optional[str] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
class CalendarEntryCreate(CalendarEntryBase):
|
||||
pass
|
||||
|
||||
class CalendarEntryResponse(CalendarEntryBase):
|
||||
id: int
|
||||
created_at: datetime
|
||||
updated_at: Optional[datetime] = None
|
||||
user_id: int
|
||||
|
||||
class Config:
|
||||
orm_mode = True
|
||||
|
||||
# Мобильные типы записей и данных
|
||||
class MobileEntryType(str, Enum):
|
||||
MENSTRUATION = "MENSTRUATION"
|
||||
OVULATION = "OVULATION"
|
||||
SPOTTING = "SPOTTING"
|
||||
DISCHARGE = "DISCHARGE"
|
||||
PAIN = "PAIN"
|
||||
MOOD = "MOOD"
|
||||
|
||||
class MobileMood(str, Enum):
|
||||
HAPPY = "HAPPY"
|
||||
SAD = "SAD"
|
||||
ANXIOUS = "ANXIOUS"
|
||||
IRRITABLE = "IRRITABLE"
|
||||
ENERGETIC = "ENERGETIC"
|
||||
TIRED = "TIRED"
|
||||
NORMAL = "NORMAL"
|
||||
|
||||
class MobileSymptom(str, Enum):
|
||||
CRAMPS = "CRAMPS"
|
||||
HEADACHE = "HEADACHE"
|
||||
BLOATING = "BLOATING"
|
||||
FATIGUE = "FATIGUE"
|
||||
NAUSEA = "NAUSEA"
|
||||
BREAST_TENDERNESS = "BREAST_TENDERNESS"
|
||||
ACNE = "ACNE"
|
||||
BACKACHE = "BACKACHE"
|
||||
|
||||
# Схемы для мобильного API
|
||||
class MobileCalendarEntry(BaseModel):
|
||||
date: date
|
||||
type: MobileEntryType
|
||||
flow_intensity: Optional[int] = None # 1-5 scale
|
||||
mood: Optional[MobileMood] = None
|
||||
symptoms: Optional[List[str]] = None
|
||||
notes: Optional[str] = None
|
||||
|
||||
# Инициализация приложения
|
||||
app = FastAPI(title="Calendar Service API",
|
||||
description="API для работы с календарём здоровья",
|
||||
version="1.0.0")
|
||||
|
||||
# CORS
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# Настройка логирования
|
||||
logging.basicConfig(level=logging.INFO)
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# Конвертеры форматов
|
||||
def mobile_to_standard_entry(mobile_entry: MobileCalendarEntry) -> CalendarEntryCreate:
|
||||
"""Конвертирует запись из мобильного формата в стандартный"""
|
||||
|
||||
# Отображение типов записей
|
||||
entry_type_mapping = {
|
||||
MobileEntryType.MENSTRUATION: "period",
|
||||
MobileEntryType.OVULATION: "ovulation",
|
||||
MobileEntryType.SPOTTING: "spotting",
|
||||
MobileEntryType.DISCHARGE: "discharge",
|
||||
MobileEntryType.PAIN: "pain",
|
||||
MobileEntryType.MOOD: "mood"
|
||||
}
|
||||
|
||||
# Отображение интенсивности потока
|
||||
flow_intensity_mapping = {
|
||||
1: "very_light",
|
||||
2: "light",
|
||||
3: "medium",
|
||||
4: "heavy",
|
||||
5: "very_heavy"
|
||||
}
|
||||
|
||||
# Отображение настроения
|
||||
mood_mapping = {
|
||||
MobileMood.HAPPY: "happy",
|
||||
MobileMood.SAD: "sad",
|
||||
MobileMood.ANXIOUS: "anxious",
|
||||
MobileMood.IRRITABLE: "irritable",
|
||||
MobileMood.ENERGETIC: "energetic",
|
||||
MobileMood.TIRED: "tired",
|
||||
MobileMood.NORMAL: "normal"
|
||||
}
|
||||
|
||||
# Сопоставляем симптомы
|
||||
symptoms_str = ""
|
||||
if mobile_entry.symptoms:
|
||||
symptoms_str = ", ".join(symptom.lower() for symptom in mobile_entry.symptoms)
|
||||
|
||||
# Определяем тип записи
|
||||
entry_type = entry_type_mapping.get(mobile_entry.type, "other")
|
||||
|
||||
# Преобразуем интенсивность потока
|
||||
flow_intensity = None
|
||||
if mobile_entry.flow_intensity and mobile_entry.type == MobileEntryType.MENSTRUATION:
|
||||
flow_intensity = flow_intensity_mapping.get(mobile_entry.flow_intensity)
|
||||
|
||||
# Преобразуем настроение
|
||||
mood = None
|
||||
if mobile_entry.mood:
|
||||
mood = mood_mapping.get(mobile_entry.mood)
|
||||
|
||||
# Создаем стандартную запись
|
||||
standard_entry = CalendarEntryCreate(
|
||||
entry_date=mobile_entry.date,
|
||||
entry_type=entry_type,
|
||||
flow_intensity=flow_intensity,
|
||||
period_symptoms="" if entry_type != "period" else symptoms_str,
|
||||
mood=mood,
|
||||
symptoms=symptoms_str if entry_type != "period" else "",
|
||||
notes=mobile_entry.notes,
|
||||
energy_level=None,
|
||||
sleep_hours=None,
|
||||
medications=""
|
||||
)
|
||||
|
||||
return standard_entry
|
||||
|
||||
def standard_to_mobile_entry(entry: CalendarEntryResponse) -> Dict[str, Any]:
|
||||
"""Конвертирует запись из стандартного формата в мобильный"""
|
||||
|
||||
# Обратное отображение типов записей
|
||||
entry_type_mapping = {
|
||||
"period": MobileEntryType.MENSTRUATION,
|
||||
"ovulation": MobileEntryType.OVULATION,
|
||||
"spotting": MobileEntryType.SPOTTING,
|
||||
"discharge": MobileEntryType.DISCHARGE,
|
||||
"pain": MobileEntryType.PAIN,
|
||||
"mood": MobileEntryType.MOOD
|
||||
}
|
||||
|
||||
# Обратное отображение интенсивности потока
|
||||
flow_intensity_mapping = {
|
||||
"very_light": 1,
|
||||
"light": 2,
|
||||
"medium": 3,
|
||||
"heavy": 4,
|
||||
"very_heavy": 5
|
||||
}
|
||||
|
||||
# Обратное отображение настроения
|
||||
mood_mapping = {
|
||||
"happy": MobileMood.HAPPY,
|
||||
"sad": MobileMood.SAD,
|
||||
"anxious": MobileMood.ANXIOUS,
|
||||
"irritable": MobileMood.IRRITABLE,
|
||||
"energetic": MobileMood.ENERGETIC,
|
||||
"tired": MobileMood.TIRED,
|
||||
"normal": MobileMood.NORMAL
|
||||
}
|
||||
|
||||
# Определяем тип записи
|
||||
mobile_type = entry_type_mapping.get(entry.entry_type, MobileEntryType.MOOD)
|
||||
|
||||
# Интенсивность потока
|
||||
flow_intensity = None
|
||||
if entry.flow_intensity:
|
||||
flow_intensity = flow_intensity_mapping.get(entry.flow_intensity)
|
||||
|
||||
# Настроение
|
||||
mood = None
|
||||
if entry.mood:
|
||||
mood = mood_mapping.get(entry.mood, MobileMood.NORMAL)
|
||||
|
||||
# Симптомы
|
||||
symptoms = []
|
||||
if entry.entry_type == "period" and entry.period_symptoms:
|
||||
symptoms = [s.strip().upper() for s in entry.period_symptoms.split(',') if s.strip()]
|
||||
elif entry.symptoms:
|
||||
symptoms = [s.strip().upper() for s in entry.symptoms.split(',') if s.strip()]
|
||||
|
||||
# Создаем мобильную запись
|
||||
mobile_entry = {
|
||||
"id": entry.id,
|
||||
"date": entry.entry_date.isoformat(),
|
||||
"type": mobile_type,
|
||||
"flow_intensity": flow_intensity,
|
||||
"mood": mood,
|
||||
"symptoms": symptoms,
|
||||
"notes": entry.notes,
|
||||
"created_at": entry.created_at.isoformat(),
|
||||
"updated_at": entry.updated_at.isoformat() if entry.updated_at else None
|
||||
}
|
||||
|
||||
return mobile_entry
|
||||
|
||||
# Эндпоинт для проверки здоровья сервиса
|
||||
@app.get("/health")
|
||||
def health_check():
|
||||
return {"status": "ok", "service": "calendar"}
|
||||
|
||||
# API для работы с календарем
|
||||
# 1. Стандартный эндпоинт для получения записей календаря
|
||||
@app.get("/api/v1/calendar/entries", response_model=List[CalendarEntryResponse])
|
||||
async def get_calendar_entries(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Получение списка записей календаря"""
|
||||
entries = db.query(CalendarEntryInDB).filter(
|
||||
CalendarEntryInDB.user_id == current_user["id"]
|
||||
).offset(skip).limit(limit).all()
|
||||
|
||||
return entries
|
||||
|
||||
# 2. Стандартный эндпоинт для создания записи календаря
|
||||
@app.post("/api/v1/calendar/entries", response_model=CalendarEntryResponse, status_code=201)
|
||||
async def create_calendar_entry(
|
||||
entry: CalendarEntryCreate,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Создание новой записи в календаре"""
|
||||
db_entry = CalendarEntryInDB(**entry.dict(), user_id=current_user["id"])
|
||||
db.add(db_entry)
|
||||
db.commit()
|
||||
db.refresh(db_entry)
|
||||
return db_entry
|
||||
|
||||
# 3. Поддержка legacy эндпоинта /api/v1/entries (без префикса /calendar)
|
||||
@app.get("/api/v1/entries", response_model=List[CalendarEntryResponse])
|
||||
async def get_entries_legacy(
|
||||
skip: int = 0,
|
||||
limit: int = 100,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Legacy эндпоинт для получения записей календаря"""
|
||||
return await get_calendar_entries(skip, limit, current_user, db)
|
||||
|
||||
@app.post("/api/v1/entries", response_model=CalendarEntryResponse, status_code=201)
|
||||
async def create_entry_legacy(
|
||||
entry: CalendarEntryCreate,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Legacy эндпоинт для создания записи в календаре"""
|
||||
return await create_calendar_entry(entry, current_user, db)
|
||||
|
||||
# 4. Поддержка эндпоинта /api/v1/entry (ед. число, без префикса /calendar)
|
||||
@app.post("/api/v1/entry", response_model=CalendarEntryResponse, status_code=201)
|
||||
async def create_entry_singular(
|
||||
entry: CalendarEntryCreate,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Эндпоинт для создания записи в календаре (ед. число)"""
|
||||
return await create_calendar_entry(entry, current_user, db)
|
||||
|
||||
# 5. Поддержка мобильного API с эндпоинтом /api/v1/calendar/entry
|
||||
@app.post("/api/v1/calendar/entry", status_code=201)
|
||||
async def create_mobile_calendar_entry(
|
||||
entry: MobileCalendarEntry,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Создание записи в календаре из мобильного приложения"""
|
||||
# Преобразуем мобильную запись в стандартную
|
||||
standard_entry = mobile_to_standard_entry(entry)
|
||||
|
||||
# Создаем запись в БД
|
||||
db_entry = CalendarEntryInDB(**standard_entry.dict(), user_id=current_user["id"])
|
||||
db.add(db_entry)
|
||||
db.commit()
|
||||
db.refresh(db_entry)
|
||||
|
||||
# Преобразуем ответ обратно в мобильный формат
|
||||
mobile_response = standard_to_mobile_entry(db_entry)
|
||||
|
||||
return mobile_response
|
||||
|
||||
@app.get("/api/v1/calendar/entry/{entry_id}", status_code=200)
|
||||
async def get_mobile_calendar_entry(
|
||||
entry_id: int,
|
||||
current_user: dict = Depends(get_current_user),
|
||||
db = Depends(get_db)
|
||||
):
|
||||
"""Получение записи календаря в мобильном формате"""
|
||||
db_entry = db.query(CalendarEntryInDB).filter(
|
||||
CalendarEntryInDB.id == entry_id,
|
||||
CalendarEntryInDB.user_id == current_user["id"]
|
||||
).first()
|
||||
|
||||
if not db_entry:
|
||||
raise HTTPException(status_code=404, detail="Запись не найдена")
|
||||
|
||||
# Преобразуем ответ в мобильный формат
|
||||
mobile_response = standard_to_mobile_entry(db_entry)
|
||||
|
||||
return mobile_response
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
uvicorn.run(app, host="0.0.0.0", port=8004)
|
||||
Reference in New Issue
Block a user