From 377817c62ea135d9842f48f8897fbf2f0f27dbdc Mon Sep 17 00:00:00 2001 From: Markov Date: Sun, 22 Feb 2026 19:39:59 +0100 Subject: [PATCH] feat: task key (TE-1) generated on backend, project includes chat_id --- src/tracker/api/tasks.py | 24 ++++++++++++++---------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/src/tracker/api/tasks.py b/src/tracker/api/tasks.py index f071a2b..3fcb444 100644 --- a/src/tracker/api/tasks.py +++ b/src/tracker/api/tasks.py @@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query from pydantic import BaseModel from sqlalchemy import select, update from sqlalchemy.ext.asyncio import AsyncSession -from sqlalchemy.orm import selectinload +from sqlalchemy.orm import selectinload, joinedload from tracker.database import get_db from tracker.models import Task, Step, Project @@ -29,6 +29,7 @@ class TaskOut(BaseModel): project_id: str parent_id: str | None = None number: int + key: str # e.g. "TE-1" title: str description: str | None = None type: str @@ -79,12 +80,14 @@ class AssignRequest(BaseModel): # --- Helpers --- -def _task_out(t: Task) -> dict: +def _task_out(t: Task, project_slug: str = "") -> dict: + prefix = project_slug[:2].upper() if project_slug else "XX" return { "id": str(t.id), "project_id": str(t.project_id), "parent_id": str(t.parent_id) if t.parent_id else None, "number": t.number, + "key": f"{prefix}-{t.number}", "title": t.title, "description": t.description, "type": t.type, @@ -106,7 +109,8 @@ def _task_out(t: Task) -> dict: async def _get_task(task_id: str, db: AsyncSession) -> Task: result = await db.execute( - select(Task).where(Task.id == uuid.UUID(task_id)).options(selectinload(Task.steps)) + select(Task).where(Task.id == uuid.UUID(task_id)) + .options(selectinload(Task.steps), joinedload(Task.project)) ) task = result.scalar_one_or_none() if not task: @@ -124,7 +128,7 @@ async def list_tasks( label: Optional[str] = Query(None), db: AsyncSession = Depends(get_db), ): - q = select(Task).options(selectinload(Task.steps)) + q = select(Task).options(selectinload(Task.steps), joinedload(Task.project)) if project_id: q = q.where(Task.project_id == uuid.UUID(project_id)) if status: @@ -135,13 +139,13 @@ async def list_tasks( q = q.where(Task.labels.contains([label])) q = q.order_by(Task.position, Task.created_at) result = await db.execute(q) - return [_task_out(t) for t in result.scalars()] + return [_task_out(t, t.project.slug if t.project else "") for t in result.scalars()] @router.get("/tasks/{task_id}", response_model=TaskOut) async def get_task(task_id: str, db: AsyncSession = Depends(get_db)): task = await _get_task(task_id, db) - return _task_out(task) + return _task_out(task, task.project.slug if task.project else "") @router.post("/tasks", response_model=TaskOut) @@ -180,7 +184,7 @@ async def create_task( await db.refresh(task) # Load steps task_full = await _get_task(str(task.id), db) - return _task_out(task_full) + return _task_out(task_full, task_full.project.slug if task_full.project else "") @router.patch("/tasks/{task_id}", response_model=TaskOut) @@ -208,7 +212,7 @@ async def update_task(task_id: str, req: TaskUpdate, db: AsyncSession = Depends( await db.commit() await db.refresh(task) - return _task_out(task) + return _task_out(task, task.project.slug if task.project else "") @router.delete("/tasks/{task_id}") @@ -232,7 +236,7 @@ async def take_task(task_id: str, slug: str = Query(...), db: AsyncSession = Dep task.watchers = (task.watchers or []) + [slug] await db.commit() await db.refresh(task) - return _task_out(task) + return _task_out(task, task.project.slug if task.project else "") @router.post("/tasks/{task_id}/reject") @@ -255,7 +259,7 @@ async def assign_task(task_id: str, req: AssignRequest, db: AsyncSession = Depen task.watchers = (task.watchers or []) + [req.assignee_slug] await db.commit() await db.refresh(task) - return _task_out(task) + return _task_out(task, task.project.slug if task.project else "") @router.post("/tasks/{task_id}/watch")