Files
home_hub/backend/app/api/todos.py
T
gilles ec87bc091d feat(sse): sync temps réel multi-appareils via Server-Sent Events v0.5.8
- 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>
2026-05-25 20:12:02 +02:00

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