M2 labels backend: labels + note_labels, CRUD, note-label set, filter
- Label + NoteLabel models; migration 0004 (labels unique per owner + note_labels join, cascade). - /api/labels: list/create(idempotent)/rename(clash-checked)/delete, owner-scoped. - PUT /api/notes/<id>/labels to set a note's labels (validated against owned). - Note responses now include labels[] (merged via one explicit join query — no lazy relationship); GET /api/notes?...&label=<id> filters by label. - DB-free auth-guard tests for labels endpoints. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FRgehjoz7Yv8LkUfADxACm
This commit is contained in:
@@ -0,0 +1,35 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import uuid
|
||||
from datetime import datetime
|
||||
|
||||
from sqlalchemy import DateTime, ForeignKey, Text, UniqueConstraint, func
|
||||
from sqlalchemy.dialects.postgresql import UUID
|
||||
from sqlalchemy.orm import Mapped, mapped_column
|
||||
|
||||
from . import Base
|
||||
|
||||
|
||||
class Label(Base):
|
||||
__tablename__ = "labels"
|
||||
__table_args__ = (UniqueConstraint("owner_id", "name", name="uq_labels_owner_name"),)
|
||||
|
||||
id: Mapped[uuid.UUID] = mapped_column(UUID(as_uuid=True), primary_key=True, default=uuid.uuid4)
|
||||
owner_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("users.id", ondelete="CASCADE"), nullable=False
|
||||
)
|
||||
name: Mapped[str] = mapped_column(Text(), nullable=False)
|
||||
created_at: Mapped[datetime] = mapped_column(DateTime(timezone=True), nullable=False, server_default=func.now())
|
||||
|
||||
|
||||
class NoteLabel(Base):
|
||||
"""Join between a note and a label (both owned by the same user)."""
|
||||
|
||||
__tablename__ = "note_labels"
|
||||
|
||||
note_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
label_id: Mapped[uuid.UUID] = mapped_column(
|
||||
UUID(as_uuid=True), ForeignKey("labels.id", ondelete="CASCADE"), primary_key=True
|
||||
)
|
||||
Reference in New Issue
Block a user