init
This commit is contained in:
121 files changed
+17641
No files matched your search
@@ -0,0 +1,121 @@
|
||||
"""Task endpoints: the aggregated list, refreshing it, and acting on it."""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from fastapi import APIRouter, HTTPException, Query
|
||||
|
||||
from app.api.deps import DbSession, Settings
|
||||
from app.api.schemas import TaskCreateRequest, TaskDoneRequest, TaskUpdateRequest
|
||||
from app.models import TaskRecord
|
||||
from app.services import tasks_service
|
||||
from app.sources.registry import SOURCE_LABELS, check_source
|
||||
from app.timeutil import ensure_utc, local_date
|
||||
|
||||
router = APIRouter(prefix="/api/tasks", tags=["tasks"])
|
||||
|
||||
|
||||
def task_dict(record: TaskRecord, today) -> dict:
|
||||
return {
|
||||
"id": record.id,
|
||||
"source": record.source,
|
||||
"source_label": SOURCE_LABELS.get(record.source, record.source),
|
||||
"external_id": record.external_id,
|
||||
"title": record.title,
|
||||
"description": record.description,
|
||||
"url": record.url,
|
||||
"project": record.project,
|
||||
"status": record.status,
|
||||
"priority": record.priority,
|
||||
"due_date": record.due_date.isoformat() if record.due_date else None,
|
||||
"start_date": record.start_date.isoformat() if record.start_date else None,
|
||||
"labels": record.labels or [],
|
||||
"is_done": record.is_done,
|
||||
"completed_at": ensure_utc(record.completed_at).isoformat() if record.completed_at else None,
|
||||
"pinned": record.pinned,
|
||||
"stale": record.stale,
|
||||
"estimated_pomodoros": record.estimated_pomodoros,
|
||||
"days_overdue": (today - record.due_date).days if record.due_date and record.due_date < today else 0,
|
||||
"bucket": _bucket_of(record, today),
|
||||
}
|
||||
|
||||
|
||||
def _bucket_of(record: TaskRecord, today) -> str:
|
||||
if record.due_date is None:
|
||||
return "someday"
|
||||
if record.due_date < today:
|
||||
return "overdue"
|
||||
if record.due_date == today:
|
||||
return "today"
|
||||
return "upcoming"
|
||||
|
||||
|
||||
@router.get("")
|
||||
def list_tasks(
|
||||
session: DbSession,
|
||||
settings: Settings,
|
||||
include_done: bool = Query(default=False),
|
||||
include_stale: bool = Query(default=False),
|
||||
source: list[str] | None = Query(default=None),
|
||||
) -> dict:
|
||||
today = local_date(settings.timezone)
|
||||
records = tasks_service.list_tasks(
|
||||
session, include_done=include_done, include_stale=include_stale, sources=source
|
||||
)
|
||||
buckets = tasks_service.bucket_tasks(records, today)
|
||||
return {
|
||||
"date": today.isoformat(),
|
||||
"tasks": [task_dict(record, today) for record in records],
|
||||
"counts": {name: len(items) for name, items in buckets.items()},
|
||||
"sources": sorted({record.source for record in records}),
|
||||
}
|
||||
|
||||
|
||||
@router.post("/refresh")
|
||||
def refresh(session: DbSession, settings: Settings) -> dict:
|
||||
"""Pull every enabled source now. Failures come back per source, not as a 500."""
|
||||
return tasks_service.refresh_tasks(session, settings)
|
||||
|
||||
|
||||
@router.post("")
|
||||
def create(body: TaskCreateRequest, session: DbSession, settings: Settings) -> dict:
|
||||
record = tasks_service.create_local_task(
|
||||
session,
|
||||
title=body.title,
|
||||
description=body.description,
|
||||
due_date=body.due_date,
|
||||
priority=body.priority,
|
||||
project=body.project,
|
||||
estimated_pomodoros=body.estimated_pomodoros,
|
||||
)
|
||||
return task_dict(record, local_date(settings.timezone))
|
||||
|
||||
|
||||
@router.patch("/{task_id}")
|
||||
def update(task_id: int, body: TaskUpdateRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
record = tasks_service.update_task(session, task_id, **body.model_dump(exclude_unset=True))
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
return task_dict(record, local_date(settings.timezone))
|
||||
|
||||
|
||||
@router.post("/{task_id}/done")
|
||||
def mark_done(task_id: int, body: TaskDoneRequest, session: DbSession, settings: Settings) -> dict:
|
||||
try:
|
||||
record, warning = tasks_service.complete_task(session, settings, task_id, done=body.done)
|
||||
except LookupError as exc:
|
||||
raise HTTPException(status_code=404, detail=str(exc)) from exc
|
||||
return {"task": task_dict(record, local_date(settings.timezone)), "warning": warning}
|
||||
|
||||
|
||||
@router.delete("/{task_id}")
|
||||
def delete(task_id: int, session: DbSession) -> dict:
|
||||
tasks_service.delete_task(session, task_id)
|
||||
return {"deleted": task_id}
|
||||
|
||||
|
||||
@router.post("/sources/{name}/test")
|
||||
def test_source(name: str, settings: Settings) -> dict:
|
||||
"""What the settings page's "Test connection" button calls."""
|
||||
ok, detail = check_source(settings, name)
|
||||
return {"source": name, "ok": ok, "detail": detail}
|
||||
Reference in new issue
Block a user