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

This commit is contained in:
2025-09-26 14:00:59 +09:00
parent 7651c01245
commit 0724018895
2 changed files with 107 additions and 98 deletions

View File

@@ -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)

View File

@@ -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()