feat: task key (TE-1) generated on backend, project includes chat_id
Some checks failed
Deploy Tracker / deploy (push) Failing after 0s

This commit is contained in:
Markov 2026-02-22 19:39:59 +01:00
parent 999b049a9d
commit 377817c62e

View File

@ -7,7 +7,7 @@ from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel from pydantic import BaseModel
from sqlalchemy import select, update from sqlalchemy import select, update
from sqlalchemy.ext.asyncio import AsyncSession 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.database import get_db
from tracker.models import Task, Step, Project from tracker.models import Task, Step, Project
@ -29,6 +29,7 @@ class TaskOut(BaseModel):
project_id: str project_id: str
parent_id: str | None = None parent_id: str | None = None
number: int number: int
key: str # e.g. "TE-1"
title: str title: str
description: str | None = None description: str | None = None
type: str type: str
@ -79,12 +80,14 @@ class AssignRequest(BaseModel):
# --- Helpers --- # --- 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 { return {
"id": str(t.id), "id": str(t.id),
"project_id": str(t.project_id), "project_id": str(t.project_id),
"parent_id": str(t.parent_id) if t.parent_id else None, "parent_id": str(t.parent_id) if t.parent_id else None,
"number": t.number, "number": t.number,
"key": f"{prefix}-{t.number}",
"title": t.title, "title": t.title,
"description": t.description, "description": t.description,
"type": t.type, "type": t.type,
@ -106,7 +109,8 @@ def _task_out(t: Task) -> dict:
async def _get_task(task_id: str, db: AsyncSession) -> Task: async def _get_task(task_id: str, db: AsyncSession) -> Task:
result = await db.execute( 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() task = result.scalar_one_or_none()
if not task: if not task:
@ -124,7 +128,7 @@ async def list_tasks(
label: Optional[str] = Query(None), label: Optional[str] = Query(None),
db: AsyncSession = Depends(get_db), 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: if project_id:
q = q.where(Task.project_id == uuid.UUID(project_id)) q = q.where(Task.project_id == uuid.UUID(project_id))
if status: if status:
@ -135,13 +139,13 @@ async def list_tasks(
q = q.where(Task.labels.contains([label])) q = q.where(Task.labels.contains([label]))
q = q.order_by(Task.position, Task.created_at) q = q.order_by(Task.position, Task.created_at)
result = await db.execute(q) 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) @router.get("/tasks/{task_id}", response_model=TaskOut)
async def get_task(task_id: str, db: AsyncSession = Depends(get_db)): async def get_task(task_id: str, db: AsyncSession = Depends(get_db)):
task = await _get_task(task_id, 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) @router.post("/tasks", response_model=TaskOut)
@ -180,7 +184,7 @@ async def create_task(
await db.refresh(task) await db.refresh(task)
# Load steps # Load steps
task_full = await _get_task(str(task.id), db) 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) @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.commit()
await db.refresh(task) await db.refresh(task)
return _task_out(task) return _task_out(task, task.project.slug if task.project else "")
@router.delete("/tasks/{task_id}") @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] task.watchers = (task.watchers or []) + [slug]
await db.commit() await db.commit()
await db.refresh(task) 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") @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] task.watchers = (task.watchers or []) + [req.assignee_slug]
await db.commit() await db.commit()
await db.refresh(task) 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") @router.post("/tasks/{task_id}/watch")