Files
bvandeusenandClaude Opus 4.8 4d1fc1bdf9
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 5s
CI & Build / Python tests (push) Successful in 9s
CI & Build / Build & push image (push) Successful in 32s
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
2026-07-19 21:43:21 -04:00

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")