Backend: - Add Literal types for status/priority fields (project_task, todo, project schemas) - Add AccentColor Literal validation to prevent CSS injection (settings schema) - Add PIN max-length (72 char bcrypt limit) validation - Fix event date filtering to use correct range overlap logic - Add revocation check to auth_status endpoint for consistency - Config: env-aware SECRET_KEY fail-fast, configurable COOKIE_SECURE Frontend: - Add withCredentials to axios for cross-origin cookie support - Replace .toISOString() with local date formatter in DashboardPage - Replace `as any` casts with proper indexed type access in forms - Nginx: add CSP, Referrer-Policy headers; remove deprecated X-XSS-Protection - Nginx: duplicate security headers in static asset location block Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
123 lines
3.7 KiB
Python
123 lines
3.7 KiB
Python
from fastapi import APIRouter, Depends, HTTPException, Query
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
from sqlalchemy import select
|
|
from typing import Optional, List
|
|
from datetime import date
|
|
|
|
from app.database import get_db
|
|
from app.models.calendar_event import CalendarEvent
|
|
from app.schemas.calendar_event import CalendarEventCreate, CalendarEventUpdate, CalendarEventResponse
|
|
from app.routers.auth import get_current_session
|
|
from app.models.settings import Settings
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=List[CalendarEventResponse])
|
|
async def get_events(
|
|
start: Optional[date] = Query(None),
|
|
end: Optional[date] = Query(None),
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Settings = Depends(get_current_session)
|
|
):
|
|
"""Get all calendar events with optional date range filtering."""
|
|
query = select(CalendarEvent)
|
|
|
|
if start:
|
|
query = query.where(CalendarEvent.end_datetime >= start)
|
|
|
|
if end:
|
|
query = query.where(CalendarEvent.start_datetime <= end)
|
|
|
|
query = query.order_by(CalendarEvent.start_datetime.asc())
|
|
|
|
result = await db.execute(query)
|
|
events = result.scalars().all()
|
|
|
|
return events
|
|
|
|
|
|
@router.post("/", response_model=CalendarEventResponse, status_code=201)
|
|
async def create_event(
|
|
event: CalendarEventCreate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Settings = Depends(get_current_session)
|
|
):
|
|
"""Create a new calendar event."""
|
|
if event.end_datetime < event.start_datetime:
|
|
raise HTTPException(status_code=400, detail="End datetime must be after start datetime")
|
|
|
|
new_event = CalendarEvent(**event.model_dump())
|
|
db.add(new_event)
|
|
await db.commit()
|
|
await db.refresh(new_event)
|
|
|
|
return new_event
|
|
|
|
|
|
@router.get("/{event_id}", response_model=CalendarEventResponse)
|
|
async def get_event(
|
|
event_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Settings = Depends(get_current_session)
|
|
):
|
|
"""Get a specific calendar event by ID."""
|
|
result = await db.execute(select(CalendarEvent).where(CalendarEvent.id == event_id))
|
|
event = result.scalar_one_or_none()
|
|
|
|
if not event:
|
|
raise HTTPException(status_code=404, detail="Calendar event not found")
|
|
|
|
return event
|
|
|
|
|
|
@router.put("/{event_id}", response_model=CalendarEventResponse)
|
|
async def update_event(
|
|
event_id: int,
|
|
event_update: CalendarEventUpdate,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Settings = Depends(get_current_session)
|
|
):
|
|
"""Update a calendar event."""
|
|
result = await db.execute(select(CalendarEvent).where(CalendarEvent.id == event_id))
|
|
event = result.scalar_one_or_none()
|
|
|
|
if not event:
|
|
raise HTTPException(status_code=404, detail="Calendar event not found")
|
|
|
|
update_data = event_update.model_dump(exclude_unset=True)
|
|
|
|
# Validate datetime order if both are being updated
|
|
start = update_data.get("start_datetime", event.start_datetime)
|
|
end = update_data.get("end_datetime", event.end_datetime)
|
|
|
|
if end < start:
|
|
raise HTTPException(status_code=400, detail="End datetime must be after start datetime")
|
|
|
|
for key, value in update_data.items():
|
|
setattr(event, key, value)
|
|
|
|
await db.commit()
|
|
await db.refresh(event)
|
|
|
|
return event
|
|
|
|
|
|
@router.delete("/{event_id}", status_code=204)
|
|
async def delete_event(
|
|
event_id: int,
|
|
db: AsyncSession = Depends(get_db),
|
|
current_user: Settings = Depends(get_current_session)
|
|
):
|
|
"""Delete a calendar event."""
|
|
result = await db.execute(select(CalendarEvent).where(CalendarEvent.id == event_id))
|
|
event = result.scalar_one_or_none()
|
|
|
|
if not event:
|
|
raise HTTPException(status_code=404, detail="Calendar event not found")
|
|
|
|
await db.delete(event)
|
|
await db.commit()
|
|
|
|
return None
|