From 009789ad8bed91a31921a7afac708781b5536742 Mon Sep 17 00:00:00 2001 From: markov Date: Sat, 28 Feb 2026 00:42:03 +0100 Subject: [PATCH] Global labels: remove project_id, endpoints at /labels Labels are now app-wide, not project-scoped. GET/POST /labels, PATCH/DELETE /labels/{id} --- src/tracker/api/labels.py | 24 +++++++++--------------- src/tracker/models/task.py | 7 ++----- 2 files changed, 11 insertions(+), 20 deletions(-) diff --git a/src/tracker/api/labels.py b/src/tracker/api/labels.py index 82133b8..83552fc 100644 --- a/src/tracker/api/labels.py +++ b/src/tracker/api/labels.py @@ -1,4 +1,4 @@ -"""Labels API — CRUD for project-scoped labels.""" +"""Labels API — CRUD for global labels.""" import uuid @@ -26,31 +26,27 @@ class LabelUpdate(BaseModel): class LabelOut(BaseModel): id: str - project_id: str name: str color: str -@router.get("/projects/{project_id}/labels", response_model=list[LabelOut]) -async def list_labels(project_id: str, db: AsyncSession = Depends(get_db)): - result = await db.execute( - select(Label).where(Label.project_id == uuid.UUID(project_id)).order_by(Label.name) - ) - return [LabelOut(id=str(l.id), project_id=str(l.project_id), name=l.name, color=l.color) for l in result.scalars()] +@router.get("/labels", response_model=list[LabelOut]) +async def list_labels(db: AsyncSession = Depends(get_db)): + result = await db.execute(select(Label).order_by(Label.name)) + return [LabelOut(id=str(l.id), name=l.name, color=l.color) for l in result.scalars()] -@router.post("/projects/{project_id}/labels", response_model=LabelOut) +@router.post("/labels", response_model=LabelOut) async def create_label( - project_id: str, req: LabelCreate, current_member: Member = Depends(get_current_member), db: AsyncSession = Depends(get_db), ): - label = Label(project_id=uuid.UUID(project_id), name=req.name, color=req.color) + label = Label(name=req.name, color=req.color) db.add(label) await db.commit() await db.refresh(label) - return LabelOut(id=str(label.id), project_id=str(label.project_id), name=label.name, color=label.color) + return LabelOut(id=str(label.id), name=label.name, color=label.color) @router.patch("/labels/{label_id}", response_model=LabelOut) @@ -70,7 +66,7 @@ async def update_label( label.color = req.color await db.commit() await db.refresh(label) - return LabelOut(id=str(label.id), project_id=str(label.project_id), name=label.name, color=label.color) + return LabelOut(id=str(label.id), name=label.name, color=label.color) @router.delete("/labels/{label_id}") @@ -83,8 +79,6 @@ async def delete_label( label = result.scalar_one_or_none() if not label: raise HTTPException(404, "Label not found") - # Remove task associations - await db.execute(select(TaskLabel).where(TaskLabel.label_id == label.id)) await db.delete(label) await db.commit() return {"ok": True} diff --git a/src/tracker/models/task.py b/src/tracker/models/task.py index 0441533..9709345 100644 --- a/src/tracker/models/task.py +++ b/src/tracker/models/task.py @@ -54,15 +54,12 @@ class Step(Base): class Label(Base): - """Project-scoped labels (like Jira).""" + """Global labels (app-wide).""" __tablename__ = "labels" - project_id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), ForeignKey("projects.id"), nullable=False) - name: Mapped[str] = mapped_column(String(100), nullable=False) + name: Mapped[str] = mapped_column(String(100), unique=True, nullable=False) color: Mapped[str] = mapped_column(String(7), default="#6366f1") # hex color - project: Mapped["Project"] = relationship() - class TaskLabel(Base): """Many-to-many: tasks ↔ labels."""