diff --git a/services/api_gateway/main.py b/services/api_gateway/main.py index 0d02d6b..41d5f54 100644 --- a/services/api_gateway/main.py +++ b/services/api_gateway/main.py @@ -771,5 +771,7 @@ app.openapi = custom_openapi if __name__ == "__main__": 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) diff --git a/services/calendar_service/main.py b/services/calendar_service/main.py index 3c6c3e9..d2315ba 100644 --- a/services/calendar_service/main.py +++ b/services/calendar_service/main.py @@ -1,16 +1,17 @@ from datetime import date, datetime, timedelta -from enum import Enum -from typing import List, Optional +from typing import Dict, List, Optional from fastapi import Depends, FastAPI, HTTPException, Query from fastapi.middleware.cors import CORSMiddleware -from pydantic import BaseModel, Field +from pydantic import BaseModel from sqlalchemy import and_, desc, select from sqlalchemy.ext.asyncio import AsyncSession from services.calendar_service.models import CalendarEntry, CycleData, HealthInsights -from services.user_service.main import get_current_user -from services.user_service.models import User +from services.calendar_service.schemas import (CalendarEntryCreate, CalendarEntryResponse, + 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.database import get_db @@ -32,66 +33,6 @@ async def health_check(): 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): id: int 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) async def create_calendar_entry( entry_data: CalendarEntryCreate, - current_user: User = Depends(get_current_user), + current_user: Dict = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Create new calendar entry""" @@ -195,7 +136,7 @@ async def create_calendar_entry( existing = await db.execute( select(CalendarEntry).filter( and_( - CalendarEntry.user_id == current_user.id, + CalendarEntry.user_id == current_user["user_id"], CalendarEntry.entry_date == entry_data.entry_date, 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" ) - db_entry = CalendarEntry( - user_id=current_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, - ) + try: + db_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(db_entry) - await db.commit() - await db.refresh(db_entry) + db.add(db_entry) + await db.commit() + 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 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) @@ -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]) async def get_calendar_entries( - current_user: User = Depends(get_current_user), + current_user: Dict = Depends(get_current_user), db: AsyncSession = Depends(get_db), start_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""" - query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user.id) + query = select(CalendarEntry).filter(CalendarEntry.user_id == current_user["user_id"]) if 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) 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 current_cycle = await db.execute( select(CycleData) - .filter(CycleData.user_id == current_user.id) + .filter(CycleData.user_id == current_user["user_id"]) .order_by(desc(CycleData.cycle_start_date)) .limit(1) ) @@ -338,7 +287,7 @@ async def get_cycle_overview( # Calculate regularity cycles = await db.execute( select(CycleData) - .filter(CycleData.user_id == current_user.id) + .filter(CycleData.user_id == current_user["user_id"]) .order_by(desc(CycleData.cycle_start_date)) .limit(6) ) @@ -370,7 +319,7 @@ async def get_cycle_overview( @app.get("/api/v1/insights", response_model=List[HealthInsightResponse]) async def get_health_insights( - current_user: User = Depends(get_current_user), + current_user: Dict = Depends(get_current_user), db: AsyncSession = Depends(get_db), limit: int = Query(10, ge=1, le=50), ): @@ -379,7 +328,7 @@ async def get_health_insights( result = await db.execute( select(HealthInsights) .filter( - HealthInsights.user_id == current_user.id, + HealthInsights.user_id == current_user["user_id"], HealthInsights.is_dismissed == False, ) .order_by(desc(HealthInsights.created_at)) @@ -395,13 +344,13 @@ async def get_all_calendar_entries( start_date: Optional[date] = None, end_date: Optional[date] = 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), limit: int = Query(100, ge=1, le=500), ): """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: 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] +@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}") async def delete_calendar_entry( entry_id: int, - current_user: User = Depends(get_current_user), + current_user: Dict = Depends(get_current_user), db: AsyncSession = Depends(get_db), ): """Delete calendar entry""" result = await db.execute( 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()