Upload app/api/v1/tags.py

This commit is contained in:
2026-06-03 23:52:10 +00:00
parent 93f1c73321
commit 62b429b0ce
+98
View File
@@ -0,0 +1,98 @@
"""Tag API endpoints (R-2: polymorphic parent validation)."""
from __future__ import annotations
from fastapi import APIRouter, Depends, HTTPException, Response, status
from sqlalchemy.ext.asyncio import AsyncSession
from app.core.db import get_db
from app.core.deps import get_current_user
from app.models.user import User
from app.schemas.tag import TagCreate, TagLinkCreate, TagLinkOut, TagOut
from app.services.tag_service import (
DuplicateTagLink,
InvalidParent,
TagNotFound,
create_tag as svc_create_tag,
link_tag as svc_link_tag,
list_tags as svc_list_tags,
unlink_tag as svc_unlink_tag,
)
router = APIRouter(prefix="/tags", tags=["tags"])
@router.post(
"/",
response_model=TagOut,
status_code=status.HTTP_201_CREATED,
summary="Create a new tag",
)
async def create_tag(
payload: TagCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> TagOut:
tag = await svc_create_tag(db, payload, org_id=current_user.org_id)
return TagOut.model_validate(tag)
@router.get(
"/",
response_model=list[TagOut],
summary="List all tags in the org",
)
async def list_tags(
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> list[TagOut]:
tags = await svc_list_tags(db, org_id=current_user.org_id)
return [TagOut.model_validate(t) for t in tags]
@router.post(
"/link",
response_model=TagLinkOut,
status_code=status.HTTP_201_CREATED,
summary="Link a tag to an entity (R-2: validates parent exists)",
)
async def link_tag_endpoint(
payload: TagLinkCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> TagLinkOut:
try:
link = await svc_link_tag(db, payload, org_id=current_user.org_id)
except InvalidParent as e:
raise HTTPException(status_code=404, detail=str(e)) from e
except TagNotFound as e:
raise HTTPException(status_code=404, detail=str(e)) from e
except DuplicateTagLink as e:
raise HTTPException(status_code=409, detail=str(e)) from e
return TagLinkOut.model_validate(link)
@router.delete(
"/link",
status_code=status.HTTP_204_NO_CONTENT,
response_class=Response,
summary="Unlink a tag from an entity",
)
async def unlink_tag_endpoint(
payload: TagLinkCreate,
current_user: User = Depends(get_current_user),
db: AsyncSession = Depends(get_db),
) -> Response:
deleted = await svc_unlink_tag(
db,
tag_id=payload.tag_id,
parent_type=payload.parent_type,
parent_id=payload.parent_id,
org_id=current_user.org_id,
)
if not deleted:
raise HTTPException(status_code=404, detail="Tag link not found")
return Response(status_code=status.HTTP_204_NO_CONTENT)
__all__ = ["router"]