ec87bc091d
- Broadcaster asyncio.Queue avec keepalive 25s (prévient timeout proxy) - Endpoint GET /api/events/stream (StreamingResponse text/event-stream) - Broadcast notes_changed / todos_changed / shopping_changed sur toutes mutations - Hook useServerEvents: EventSource avec reconnexion automatique (3s) - Pages Notes, Todos, Shopping abonnées aux événements SSE - nginx: location SSE dédiée (proxy_buffering off, timeout 24h) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
117 lines
3.5 KiB
Python
117 lines
3.5 KiB
Python
import uuid
|
|
from datetime import datetime, timedelta, timezone
|
|
from fastapi import APIRouter, Depends, HTTPException
|
|
from fastapi.responses import Response
|
|
from sqlalchemy import select, and_
|
|
from sqlalchemy.ext.asyncio import AsyncSession
|
|
|
|
from app.core.broadcaster import broadcaster
|
|
from app.core.database import get_session
|
|
from app.models.todos import TodoItem
|
|
from app.schemas.todos import TodoCreate, TodoUpdate, PostponeRequest, TodoResponse
|
|
|
|
router = APIRouter()
|
|
|
|
|
|
@router.get("/", response_model=list[TodoResponse])
|
|
async def list_todos(
|
|
domain: str | None = None,
|
|
status: str | None = "pending",
|
|
priority: str | None = None,
|
|
tag: str | None = None,
|
|
due_after: datetime | None = None,
|
|
due_before: datetime | None = None,
|
|
limit: int = 200,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
conditions = []
|
|
if domain:
|
|
conditions.append(TodoItem.domains.contains([domain]))
|
|
if status:
|
|
conditions.append(TodoItem.status == status)
|
|
if priority:
|
|
conditions.append(TodoItem.priority == priority)
|
|
if tag:
|
|
conditions.append(TodoItem.tags.contains([tag]))
|
|
if due_after:
|
|
conditions.append(TodoItem.due_date >= due_after)
|
|
if due_before:
|
|
conditions.append(TodoItem.due_date <= due_before)
|
|
|
|
stmt = select(TodoItem)
|
|
if conditions:
|
|
stmt = stmt.where(and_(*conditions))
|
|
stmt = stmt.order_by(TodoItem.due_date.asc().nulls_last(), TodoItem.created_at.desc())
|
|
stmt = stmt.limit(limit)
|
|
result = await session.execute(stmt)
|
|
return result.scalars().all()
|
|
|
|
|
|
@router.post("/", response_model=TodoResponse, status_code=201)
|
|
async def create_todo(
|
|
payload: TodoCreate,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
item = TodoItem(**payload.model_dump())
|
|
session.add(item)
|
|
await session.commit()
|
|
await session.refresh(item)
|
|
broadcaster.broadcast("todos_changed")
|
|
return item
|
|
|
|
|
|
@router.patch("/{item_id}", response_model=TodoResponse)
|
|
async def update_todo(
|
|
item_id: uuid.UUID,
|
|
payload: TodoUpdate,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
item = await session.get(TodoItem, item_id)
|
|
if not item:
|
|
raise HTTPException(status_code=404, detail="Tâche introuvable")
|
|
|
|
for field, value in payload.model_dump(exclude_unset=True).items():
|
|
setattr(item, field, value)
|
|
item.updated_at = datetime.now(timezone.utc)
|
|
|
|
await session.commit()
|
|
await session.refresh(item)
|
|
broadcaster.broadcast("todos_changed")
|
|
return item
|
|
|
|
|
|
@router.delete("/{item_id}", status_code=204)
|
|
async def delete_todo(
|
|
item_id: uuid.UUID,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
item = await session.get(TodoItem, item_id)
|
|
if not item:
|
|
raise HTTPException(status_code=404, detail="Tâche introuvable")
|
|
await session.delete(item)
|
|
await session.commit()
|
|
broadcaster.broadcast("todos_changed")
|
|
return Response(status_code=204)
|
|
|
|
|
|
@router.post("/{item_id}/postpone", response_model=TodoResponse)
|
|
async def postpone_todo(
|
|
item_id: uuid.UUID,
|
|
payload: PostponeRequest,
|
|
session: AsyncSession = Depends(get_session),
|
|
):
|
|
item = await session.get(TodoItem, item_id)
|
|
if not item:
|
|
raise HTTPException(status_code=404, detail="Tâche introuvable")
|
|
|
|
now = datetime.now(timezone.utc)
|
|
base = item.due_date if item.due_date else now
|
|
item.due_date = base + timedelta(days=payload.days)
|
|
item.postponed_count += 1
|
|
item.updated_at = now
|
|
|
|
await session.commit()
|
|
await session.refresh(item)
|
|
broadcaster.broadcast("todos_changed")
|
|
return item
|