This commit is contained in:
@@ -771,5 +771,7 @@ app.openapi = custom_openapi
|
|||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
import uvicorn
|
import uvicorn
|
||||||
|
import os
|
||||||
|
|
||||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
port = int(os.environ.get("PORT", 8000))
|
||||||
|
uvicorn.run(app, host="0.0.0.0", port=port)
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
from datetime import date, datetime, timedelta
|
from datetime import date, datetime, timedelta
|
||||||
from enum import Enum
|
from typing import Dict, List, Optional
|
||||||
from typing import List, Optional
|
|
||||||
|
|
||||||
from fastapi import Depends, FastAPI, HTTPException, Query
|
from fastapi import Depends, FastAPI, HTTPException, Query
|
||||||
from fastapi.middleware.cors import CORSMiddleware
|
from fastapi.middleware.cors import CORSMiddleware
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel
|
||||||
from sqlalchemy import and_, desc, select
|
from sqlalchemy import and_, desc, select
|
||||||
from sqlalchemy.ext.asyncio import AsyncSession
|
from sqlalchemy.ext.asyncio import AsyncSession
|
||||||
|
|
||||||
from services.calendar_service.models import CalendarEntry, CycleData, HealthInsights
|
from services.calendar_service.models import CalendarEntry, CycleData, HealthInsights
|
||||||
from services.user_service.main import get_current_user
|
from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntryResponse,
|
||||||
from services.user_service.models import User
|
CycleDataResponse, CycleOverview, EntryType,
|
||||||
|
FlowIntensity, HealthInsightResponse, MoodType)
|
||||||
|
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
|
||||||
|
|
||||||
@@ -32,66 +33,6 @@ async def health_check():
|
|||||||
return {"status": "healthy", "service": "calendar_service"}
|
return {"status": "healthy", "service": "calendar_service"}
|
||||||
|
|
||||||
|
|
||||||
class EntryType(str, Enum):
|
|
||||||
PERIOD = "period"
|
|
||||||
OVULATION = "ovulation"
|
|
||||||
SYMPTOMS = "symptoms"
|
|
||||||
MEDICATION = "medication"
|
|
||||||
MOOD = "mood"
|
|
||||||
EXERCISE = "exercise"
|
|
||||||
APPOINTMENT = "appointment"
|
|
||||||
|
|
||||||
|
|
||||||
class FlowIntensity(str, Enum):
|
|
||||||
LIGHT = "light"
|
|
||||||
MEDIUM = "medium"
|
|
||||||
HEAVY = "heavy"
|
|
||||||
SPOTTING = "spotting"
|
|
||||||
|
|
||||||
|
|
||||||
class MoodType(str, Enum):
|
|
||||||
HAPPY = "happy"
|
|
||||||
SAD = "sad"
|
|
||||||
ANXIOUS = "anxious"
|
|
||||||
IRRITATED = "irritated"
|
|
||||||
ENERGETIC = "energetic"
|
|
||||||
TIRED = "tired"
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarEntryCreate(BaseModel):
|
|
||||||
entry_date: date
|
|
||||||
entry_type: EntryType
|
|
||||||
flow_intensity: Optional[FlowIntensity] = None
|
|
||||||
period_symptoms: Optional[str] = Field(None, max_length=500)
|
|
||||||
mood: Optional[MoodType] = None
|
|
||||||
energy_level: Optional[int] = Field(None, ge=1, le=5)
|
|
||||||
sleep_hours: Optional[int] = Field(None, ge=0, le=24)
|
|
||||||
symptoms: Optional[str] = Field(None, max_length=1000)
|
|
||||||
medications: Optional[str] = Field(None, max_length=500)
|
|
||||||
notes: Optional[str] = Field(None, max_length=1000)
|
|
||||||
|
|
||||||
|
|
||||||
class CalendarEntryResponse(BaseModel):
|
|
||||||
id: int
|
|
||||||
uuid: str
|
|
||||||
entry_date: date
|
|
||||||
entry_type: str
|
|
||||||
flow_intensity: Optional[str]
|
|
||||||
period_symptoms: Optional[str]
|
|
||||||
mood: Optional[str]
|
|
||||||
energy_level: Optional[int]
|
|
||||||
sleep_hours: Optional[int]
|
|
||||||
symptoms: Optional[str]
|
|
||||||
medications: Optional[str]
|
|
||||||
notes: Optional[str]
|
|
||||||
is_predicted: bool
|
|
||||||
confidence_score: Optional[int]
|
|
||||||
created_at: datetime
|
|
||||||
|
|
||||||
class Config:
|
|
||||||
from_attributes = True
|
|
||||||
|
|
||||||
|
|
||||||
class CycleDataResponse(BaseModel):
|
class CycleDataResponse(BaseModel):
|
||||||
id: int
|
id: int
|
||||||
cycle_start_date: date
|
cycle_start_date: date
|
||||||
@@ -186,7 +127,7 @@ async def calculate_predictions(user_id: int, db: AsyncSession):
|
|||||||
@app.post("/api/v1/entries", response_model=CalendarEntryResponse)
|
@app.post("/api/v1/entries", response_model=CalendarEntryResponse)
|
||||||
async def create_calendar_entry(
|
async def create_calendar_entry(
|
||||||
entry_data: CalendarEntryCreate,
|
entry_data: CalendarEntryCreate,
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: Dict = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Create new calendar entry"""
|
"""Create new calendar entry"""
|
||||||
@@ -195,7 +136,7 @@ async def create_calendar_entry(
|
|||||||
existing = await db.execute(
|
existing = await db.execute(
|
||||||
select(CalendarEntry).filter(
|
select(CalendarEntry).filter(
|
||||||
and_(
|
and_(
|
||||||
CalendarEntry.user_id == current_user.id,
|
CalendarEntry.user_id == current_user["user_id"],
|
||||||
CalendarEntry.entry_date == entry_data.entry_date,
|
CalendarEntry.entry_date == entry_data.entry_date,
|
||||||
CalendarEntry.entry_type == entry_data.entry_type.value,
|
CalendarEntry.entry_type == entry_data.entry_type.value,
|
||||||
)
|
)
|
||||||
@@ -206,29 +147,37 @@ async def create_calendar_entry(
|
|||||||
status_code=400, detail="Entry already exists for this date and type"
|
status_code=400, detail="Entry already exists for this date and type"
|
||||||
)
|
)
|
||||||
|
|
||||||
db_entry = CalendarEntry(
|
try:
|
||||||
user_id=current_user.id,
|
db_entry = CalendarEntry(
|
||||||
entry_date=entry_data.entry_date,
|
user_id=current_user["user_id"],
|
||||||
entry_type=entry_data.entry_type.value,
|
entry_date=entry_data.entry_date,
|
||||||
flow_intensity=entry_data.flow_intensity.value
|
entry_type=entry_data.entry_type.value,
|
||||||
if entry_data.flow_intensity
|
flow_intensity=entry_data.flow_intensity.value
|
||||||
else None,
|
if entry_data.flow_intensity
|
||||||
period_symptoms=entry_data.period_symptoms,
|
else None,
|
||||||
mood=entry_data.mood.value if entry_data.mood else None,
|
period_symptoms=entry_data.period_symptoms,
|
||||||
energy_level=entry_data.energy_level,
|
mood=entry_data.mood.value if entry_data.mood else None,
|
||||||
sleep_hours=entry_data.sleep_hours,
|
energy_level=entry_data.energy_level,
|
||||||
symptoms=entry_data.symptoms,
|
sleep_hours=entry_data.sleep_hours,
|
||||||
medications=entry_data.medications,
|
symptoms=entry_data.symptoms,
|
||||||
notes=entry_data.notes,
|
medications=entry_data.medications,
|
||||||
)
|
notes=entry_data.notes,
|
||||||
|
)
|
||||||
|
|
||||||
db.add(db_entry)
|
db.add(db_entry)
|
||||||
await db.commit()
|
await db.commit()
|
||||||
await db.refresh(db_entry)
|
await db.refresh(db_entry)
|
||||||
|
|
||||||
|
import logging
|
||||||
|
logging.info(f"Created calendar entry: {db_entry.id}")
|
||||||
|
except Exception as e:
|
||||||
|
import logging
|
||||||
|
logging.error(f"Error creating calendar entry: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||||
|
|
||||||
# If this is a period entry, update cycle data
|
# If this is a period entry, update cycle data
|
||||||
if entry_data.entry_type == EntryType.PERIOD:
|
if entry_data.entry_type == EntryType.PERIOD:
|
||||||
await update_cycle_data(current_user.id, entry_data.entry_date, db)
|
await update_cycle_data(current_user["user_id"], entry_data.entry_date, db)
|
||||||
|
|
||||||
return CalendarEntryResponse.model_validate(db_entry)
|
return CalendarEntryResponse.model_validate(db_entry)
|
||||||
|
|
||||||
@@ -269,7 +218,7 @@ async def update_cycle_data(user_id: int, period_date: date, db: AsyncSession):
|
|||||||
|
|
||||||
@app.get("/api/v1/entries", response_model=List[CalendarEntryResponse])
|
@app.get("/api/v1/entries", response_model=List[CalendarEntryResponse])
|
||||||
async def get_calendar_entries(
|
async def get_calendar_entries(
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: Dict = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
start_date: Optional[date] = Query(None),
|
start_date: Optional[date] = Query(None),
|
||||||
end_date: Optional[date] = Query(None),
|
end_date: Optional[date] = Query(None),
|
||||||
@@ -278,7 +227,7 @@ async def get_calendar_entries(
|
|||||||
):
|
):
|
||||||
"""Get calendar entries with optional filtering"""
|
"""Get calendar entries with optional filtering"""
|
||||||
|
|
||||||
query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user.id)
|
query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user["user_id"])
|
||||||
|
|
||||||
if start_date:
|
if start_date:
|
||||||
query = query.filter(CalendarEntry.entry_date >= start_date)
|
query = query.filter(CalendarEntry.entry_date >= start_date)
|
||||||
@@ -297,14 +246,14 @@ async def get_calendar_entries(
|
|||||||
|
|
||||||
@app.get("/api/v1/cycle-overview", response_model=CycleOverview)
|
@app.get("/api/v1/cycle-overview", response_model=CycleOverview)
|
||||||
async def get_cycle_overview(
|
async def get_cycle_overview(
|
||||||
current_user: User = Depends(get_current_user), db: AsyncSession = Depends(get_db)
|
current_user: Dict = Depends(get_current_user), db: AsyncSession = Depends(get_db)
|
||||||
):
|
):
|
||||||
"""Get current cycle overview and predictions"""
|
"""Get current cycle overview and predictions"""
|
||||||
|
|
||||||
# Get current cycle
|
# Get current cycle
|
||||||
current_cycle = await db.execute(
|
current_cycle = await db.execute(
|
||||||
select(CycleData)
|
select(CycleData)
|
||||||
.filter(CycleData.user_id == current_user.id)
|
.filter(CycleData.user_id == current_user["user_id"])
|
||||||
.order_by(desc(CycleData.cycle_start_date))
|
.order_by(desc(CycleData.cycle_start_date))
|
||||||
.limit(1)
|
.limit(1)
|
||||||
)
|
)
|
||||||
@@ -338,7 +287,7 @@ async def get_cycle_overview(
|
|||||||
# Calculate regularity
|
# Calculate regularity
|
||||||
cycles = await db.execute(
|
cycles = await db.execute(
|
||||||
select(CycleData)
|
select(CycleData)
|
||||||
.filter(CycleData.user_id == current_user.id)
|
.filter(CycleData.user_id == current_user["user_id"])
|
||||||
.order_by(desc(CycleData.cycle_start_date))
|
.order_by(desc(CycleData.cycle_start_date))
|
||||||
.limit(6)
|
.limit(6)
|
||||||
)
|
)
|
||||||
@@ -370,7 +319,7 @@ async def get_cycle_overview(
|
|||||||
|
|
||||||
@app.get("/api/v1/insights", response_model=List[HealthInsightResponse])
|
@app.get("/api/v1/insights", response_model=List[HealthInsightResponse])
|
||||||
async def get_health_insights(
|
async def get_health_insights(
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: Dict = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
limit: int = Query(10, ge=1, le=50),
|
limit: int = Query(10, ge=1, le=50),
|
||||||
):
|
):
|
||||||
@@ -379,7 +328,7 @@ async def get_health_insights(
|
|||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(HealthInsights)
|
select(HealthInsights)
|
||||||
.filter(
|
.filter(
|
||||||
HealthInsights.user_id == current_user.id,
|
HealthInsights.user_id == current_user["user_id"],
|
||||||
HealthInsights.is_dismissed == False,
|
HealthInsights.is_dismissed == False,
|
||||||
)
|
)
|
||||||
.order_by(desc(HealthInsights.created_at))
|
.order_by(desc(HealthInsights.created_at))
|
||||||
@@ -395,13 +344,13 @@ async def get_all_calendar_entries(
|
|||||||
start_date: Optional[date] = None,
|
start_date: Optional[date] = None,
|
||||||
end_date: Optional[date] = None,
|
end_date: Optional[date] = None,
|
||||||
entry_type: Optional[EntryType] = None,
|
entry_type: Optional[EntryType] = None,
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: Dict = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
limit: int = Query(100, ge=1, le=500),
|
limit: int = Query(100, ge=1, le=500),
|
||||||
):
|
):
|
||||||
"""Get all calendar entries for the current user"""
|
"""Get all calendar entries for the current user"""
|
||||||
|
|
||||||
query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user.id)
|
query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user["user_id"])
|
||||||
|
|
||||||
if start_date:
|
if start_date:
|
||||||
query = query.filter(CalendarEntry.entry_date >= start_date)
|
query = query.filter(CalendarEntry.entry_date >= start_date)
|
||||||
@@ -418,17 +367,75 @@ async def get_all_calendar_entries(
|
|||||||
return [CalendarEntryResponse.model_validate(entry) for entry in entries]
|
return [CalendarEntryResponse.model_validate(entry) for entry in entries]
|
||||||
|
|
||||||
|
|
||||||
|
@app.post("/api/v1/calendar/entries", response_model=CalendarEntryResponse, status_code=201)
|
||||||
|
async def create_calendar_entry(
|
||||||
|
entry_data: CalendarEntryCreate,
|
||||||
|
current_user: Dict = Depends(get_current_user),
|
||||||
|
db: AsyncSession = Depends(get_db),
|
||||||
|
):
|
||||||
|
"""Create a new calendar entry"""
|
||||||
|
|
||||||
|
# Debug prints
|
||||||
|
import logging
|
||||||
|
logging.info(f"Current user: {current_user}")
|
||||||
|
logging.info(f"Entry data: {entry_data}")
|
||||||
|
|
||||||
|
try:
|
||||||
|
# Check if entry already exists for this date and type
|
||||||
|
existing = await db.execute(
|
||||||
|
select(CalendarEntry).filter(
|
||||||
|
and_(
|
||||||
|
CalendarEntry.user_id == current_user["user_id"],
|
||||||
|
CalendarEntry.entry_date == entry_data.entry_date,
|
||||||
|
CalendarEntry.entry_type == entry_data.entry_type.value,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if existing.scalars().first():
|
||||||
|
raise HTTPException(
|
||||||
|
status_code=400, detail="Entry already exists for this date and type"
|
||||||
|
)
|
||||||
|
except Exception as e:
|
||||||
|
logging.error(f"Error checking for existing entry: {str(e)}")
|
||||||
|
raise HTTPException(status_code=500, detail=f"Internal server error: {str(e)}")
|
||||||
|
|
||||||
|
# Create new calendar entry
|
||||||
|
new_entry = CalendarEntry(
|
||||||
|
user_id=current_user["user_id"],
|
||||||
|
entry_date=entry_data.entry_date,
|
||||||
|
entry_type=entry_data.entry_type.value,
|
||||||
|
flow_intensity=entry_data.flow_intensity.value if entry_data.flow_intensity else None,
|
||||||
|
period_symptoms=entry_data.period_symptoms,
|
||||||
|
mood=entry_data.mood.value if entry_data.mood else None,
|
||||||
|
energy_level=entry_data.energy_level,
|
||||||
|
sleep_hours=entry_data.sleep_hours,
|
||||||
|
symptoms=entry_data.symptoms,
|
||||||
|
medications=entry_data.medications,
|
||||||
|
notes=entry_data.notes,
|
||||||
|
)
|
||||||
|
|
||||||
|
db.add(new_entry)
|
||||||
|
await db.commit()
|
||||||
|
await db.refresh(new_entry)
|
||||||
|
|
||||||
|
# If this is a period entry, update cycle data
|
||||||
|
if entry_data.entry_type == EntryType.PERIOD:
|
||||||
|
await update_cycle_data(current_user["user_id"], entry_data.entry_date, db)
|
||||||
|
|
||||||
|
return CalendarEntryResponse.model_validate(new_entry)
|
||||||
|
|
||||||
|
|
||||||
@app.delete("/api/v1/entries/{entry_id}")
|
@app.delete("/api/v1/entries/{entry_id}")
|
||||||
async def delete_calendar_entry(
|
async def delete_calendar_entry(
|
||||||
entry_id: int,
|
entry_id: int,
|
||||||
current_user: User = Depends(get_current_user),
|
current_user: Dict = Depends(get_current_user),
|
||||||
db: AsyncSession = Depends(get_db),
|
db: AsyncSession = Depends(get_db),
|
||||||
):
|
):
|
||||||
"""Delete calendar entry"""
|
"""Delete calendar entry"""
|
||||||
|
|
||||||
result = await db.execute(
|
result = await db.execute(
|
||||||
select(CalendarEntry).filter(
|
select(CalendarEntry).filter(
|
||||||
and_(CalendarEntry.id == entry_id, CalendarEntry.user_id == current_user.id)
|
and_(CalendarEntry.id == entry_id, CalendarEntry.user_id == current_user["user_id"])
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
entry = result.scalars().first()
|
entry = result.scalars().first()
|
||||||
|
|||||||
Reference in New Issue
Block a user