- 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
41 lines
1.3 KiB
Python
41 lines
1.3 KiB
Python
"""labels + note_labels
|
|
|
|
Revision ID: 0004
|
|
Revises: 0003
|
|
Create Date: 2026-07-20
|
|
"""
|
|
from alembic import op
|
|
import sqlalchemy as sa
|
|
from sqlalchemy.dialects.postgresql import UUID
|
|
|
|
revision = "0004"
|
|
down_revision = "0003"
|
|
branch_labels = None
|
|
depends_on = None
|
|
|
|
|
|
def upgrade() -> None:
|
|
op.create_table(
|
|
"labels",
|
|
sa.Column("id", UUID(as_uuid=True), primary_key=True),
|
|
sa.Column("owner_id", UUID(as_uuid=True), sa.ForeignKey("users.id", ondelete="CASCADE"), nullable=False),
|
|
sa.Column("name", sa.Text(), nullable=False),
|
|
sa.Column("created_at", sa.DateTime(timezone=True), nullable=False, server_default=sa.func.now()),
|
|
sa.UniqueConstraint("owner_id", "name", name="uq_labels_owner_name"),
|
|
)
|
|
op.create_index("ix_labels_owner", "labels", ["owner_id"])
|
|
|
|
op.create_table(
|
|
"note_labels",
|
|
sa.Column("note_id", UUID(as_uuid=True), sa.ForeignKey("notes.id", ondelete="CASCADE"), primary_key=True),
|
|
sa.Column("label_id", UUID(as_uuid=True), sa.ForeignKey("labels.id", ondelete="CASCADE"), primary_key=True),
|
|
)
|
|
op.create_index("ix_note_labels_label", "note_labels", ["label_id"])
|
|
|
|
|
|
def downgrade() -> None:
|
|
op.drop_index("ix_note_labels_label", table_name="note_labels")
|
|
op.drop_table("note_labels")
|
|
op.drop_index("ix_labels_owner", table_name="labels")
|
|
op.drop_table("labels")
|