- End-Datum passt sich automatisch an wenn Start geändert wird (Duration bleibt erhalten) - Erstellen-Button nutzt den aktuell angesehenen Tag statt immer heute - Monatsansicht: Einzelklick = Tag auswählen, Doppelklick = Tagesansicht, Rechtsklick = Kontextmenü - CalDAV URL-Matching robuster (Normalisierung, Path-Fallback, calendar_id Parameter) - iCal-Abo-Termine sind nicht mehr bearbeitbar (Read-Only-Schutz) - Wiederkehrende Termine mit RRULE-Support (täglich/wöchentlich/monatlich/jährlich/benutzerdefiniert)
252 lines
6.3 KiB
Python
252 lines
6.3 KiB
Python
import uuid
|
|
from typing import Optional
|
|
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from pydantic import BaseModel
|
|
from sqlalchemy.orm import Session
|
|
|
|
import models
|
|
from auth import get_current_user
|
|
from database import get_db
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
class CalendarCreate(BaseModel):
|
|
name: str
|
|
color: str = "#34a853"
|
|
|
|
|
|
class CalendarUpdate(BaseModel):
|
|
name: Optional[str] = None
|
|
color: Optional[str] = None
|
|
enabled: Optional[bool] = None
|
|
|
|
|
|
class EventCreate(BaseModel):
|
|
calendar_id: int
|
|
title: str
|
|
start: str
|
|
end: str
|
|
allDay: bool = False
|
|
location: Optional[str] = None
|
|
description: Optional[str] = None
|
|
color: Optional[str] = None
|
|
rrule: Optional[str] = None
|
|
|
|
|
|
class EventUpdate(BaseModel):
|
|
title: Optional[str] = None
|
|
start: Optional[str] = None
|
|
end: Optional[str] = None
|
|
allDay: Optional[bool] = None
|
|
location: Optional[str] = None
|
|
description: Optional[str] = None
|
|
color: Optional[str] = None
|
|
rrule: Optional[str] = None
|
|
|
|
|
|
def _cal_dict(cal: models.LocalCalendar) -> dict:
|
|
return {
|
|
"id": cal.id,
|
|
"name": cal.name,
|
|
"color": cal.color,
|
|
"enabled": cal.enabled,
|
|
}
|
|
|
|
|
|
def _event_dict(ev: models.LocalEvent, cal: models.LocalCalendar) -> dict:
|
|
return {
|
|
"id": ev.uid,
|
|
"url": f"local://{ev.uid}",
|
|
"title": ev.title,
|
|
"start": ev.start,
|
|
"end": ev.end,
|
|
"allDay": ev.all_day,
|
|
"location": ev.location or "",
|
|
"description": ev.description or "",
|
|
"color": ev.color,
|
|
"rrule": ev.rrule,
|
|
"calendar_id": f"local-{cal.id}",
|
|
"calendar_name": cal.name,
|
|
"calendarColor": cal.color,
|
|
"source": "local",
|
|
}
|
|
|
|
|
|
# ── Calendar CRUD ─────────────────────────────────────────
|
|
|
|
@router.get("/calendars")
|
|
def list_calendars(
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
cals = (
|
|
db.query(models.LocalCalendar)
|
|
.filter(models.LocalCalendar.user_id == current_user.id)
|
|
.all()
|
|
)
|
|
return [_cal_dict(c) for c in cals]
|
|
|
|
|
|
@router.post("/calendars")
|
|
def create_calendar(
|
|
data: CalendarCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
cal = models.LocalCalendar(
|
|
user_id=current_user.id,
|
|
name=data.name,
|
|
color=data.color,
|
|
)
|
|
db.add(cal)
|
|
db.commit()
|
|
db.refresh(cal)
|
|
return _cal_dict(cal)
|
|
|
|
|
|
@router.put("/calendars/{calendar_id}")
|
|
def update_calendar(
|
|
calendar_id: int,
|
|
data: CalendarUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
cal = (
|
|
db.query(models.LocalCalendar)
|
|
.filter(
|
|
models.LocalCalendar.id == calendar_id,
|
|
models.LocalCalendar.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not cal:
|
|
raise HTTPException(404, "Calendar not found")
|
|
if data.name is not None:
|
|
cal.name = data.name
|
|
if data.color is not None:
|
|
cal.color = data.color
|
|
if data.enabled is not None:
|
|
cal.enabled = data.enabled
|
|
db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.delete("/calendars/{calendar_id}")
|
|
def delete_calendar(
|
|
calendar_id: int,
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
cal = (
|
|
db.query(models.LocalCalendar)
|
|
.filter(
|
|
models.LocalCalendar.id == calendar_id,
|
|
models.LocalCalendar.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not cal:
|
|
raise HTTPException(404, "Calendar not found")
|
|
db.delete(cal)
|
|
db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
# ── Event CRUD ────────────────────────────────────────────
|
|
|
|
@router.post("/events")
|
|
def create_event(
|
|
data: EventCreate,
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
cal = (
|
|
db.query(models.LocalCalendar)
|
|
.filter(
|
|
models.LocalCalendar.id == data.calendar_id,
|
|
models.LocalCalendar.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not cal:
|
|
raise HTTPException(404, "Calendar not found")
|
|
|
|
ev = models.LocalEvent(
|
|
calendar_id=cal.id,
|
|
uid=str(uuid.uuid4()),
|
|
title=data.title,
|
|
start=data.start,
|
|
end=data.end,
|
|
all_day=data.allDay,
|
|
location=data.location,
|
|
description=data.description,
|
|
color=data.color,
|
|
rrule=data.rrule,
|
|
)
|
|
db.add(ev)
|
|
db.commit()
|
|
db.refresh(ev)
|
|
return _event_dict(ev, cal)
|
|
|
|
|
|
@router.put("/events/{uid}")
|
|
def update_event(
|
|
uid: str,
|
|
data: EventUpdate,
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
ev = (
|
|
db.query(models.LocalEvent)
|
|
.join(models.LocalCalendar)
|
|
.filter(
|
|
models.LocalEvent.uid == uid,
|
|
models.LocalCalendar.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not ev:
|
|
raise HTTPException(404, "Event not found")
|
|
if data.title is not None:
|
|
ev.title = data.title
|
|
if data.start is not None:
|
|
ev.start = data.start
|
|
if data.end is not None:
|
|
ev.end = data.end
|
|
if data.allDay is not None:
|
|
ev.all_day = data.allDay
|
|
if data.location is not None:
|
|
ev.location = data.location
|
|
if data.description is not None:
|
|
ev.description = data.description
|
|
if data.color is not None:
|
|
ev.color = data.color
|
|
if data.rrule is not None:
|
|
ev.rrule = data.rrule if data.rrule else None
|
|
db.commit()
|
|
return {"ok": True}
|
|
|
|
|
|
@router.delete("/events/{uid}")
|
|
def delete_event(
|
|
uid: str,
|
|
db: Session = Depends(get_db),
|
|
current_user: models.User = Depends(get_current_user),
|
|
):
|
|
ev = (
|
|
db.query(models.LocalEvent)
|
|
.join(models.LocalCalendar)
|
|
.filter(
|
|
models.LocalEvent.uid == uid,
|
|
models.LocalCalendar.user_id == current_user.id,
|
|
)
|
|
.first()
|
|
)
|
|
if not ev:
|
|
raise HTTPException(404, "Event not found")
|
|
db.delete(ev)
|
|
db.commit()
|
|
return {"ok": True}
|