Newer
Older
gnexus-tasks / backend / app / api / task_events.py
"""Чтение журнала действий по задачам (ТЗ 3.20).

Первый пагинированный список в API: конверт `{items, total, page, per_page}`.
Потолок `per_page` — 100: журнал растёт быстро, отдавать его целиком нельзя.
"""

from fastapi import APIRouter, Query
from sqlalchemy import func, select

from app.dependencies import DbDep, UserIdDep
from app.models import TaskEvent
from app.schemas import TaskEventOut, TaskEventPage

router = APIRouter(prefix="/api/task-events", tags=["task-events"])

MAX_PER_PAGE = 100


@router.get("", response_model=TaskEventPage)
async def list_task_events(
    db: DbDep,
    user_id: UserIdDep,
    kind: str | None = Query(None, description="Тип события (created, completed, …)"),
    actor_kind: str | None = Query(None, pattern="^(user|agent)$"),
    via: str | None = Query(None, pattern="^(ui|api|mcp)$"),
    project_id: int | None = Query(None),
    task_id: int | None = Query(None),
    page: int = Query(1, ge=1),
    per_page: int = Query(50, ge=1, le=MAX_PER_PAGE),
) -> TaskEventPage:
    """Журнал пользователя, свежие записи первыми."""
    filters = [TaskEvent.user_id == user_id]
    if kind:
        filters.append(TaskEvent.kind == kind)
    if actor_kind:
        filters.append(TaskEvent.actor_kind == actor_kind)
    if via:
        filters.append(TaskEvent.via == via)
    if project_id:
        filters.append(TaskEvent.project_id == project_id)
    if task_id:
        filters.append(TaskEvent.task_id == task_id)
    total = db.scalar(select(func.count()).select_from(TaskEvent).where(*filters)) or 0
    rows = db.scalars(
        select(TaskEvent)
        .where(*filters)
        # id вторым ключом: у записей одной секунды иначе нет устойчивого порядка
        .order_by(TaskEvent.created_at.desc(), TaskEvent.id.desc())
        .limit(per_page)
        .offset((page - 1) * per_page)
    ).all()
    return TaskEventPage(
        items=[TaskEventOut.model_validate(row) for row in rows],
        total=total,
        page=page,
        per_page=per_page,
    )