Add idempotent Alembic migrations, auto-migrate on startup, update summary
Rewrite migrations to raw SQL with IF NOT EXISTS/EXCEPTION guards for full idempotency. Add notes table migration (0001), fix migration chain, and run alembic upgrade head in Dockerfile CMD on container start. Update summary.md with Phase 3.5 changes and Alembic instructions. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,33 @@
|
||||
"""create notes table
|
||||
|
||||
Revision ID: 0001
|
||||
Revises:
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
|
||||
revision = "0001"
|
||||
down_revision = None
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS notes (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
body TEXT NOT NULL DEFAULT '',
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
parent_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
""")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_notes_tags ON notes USING gin (tags)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.execute("DROP INDEX IF EXISTS ix_notes_tags")
|
||||
op.execute("DROP TABLE IF EXISTS notes")
|
||||
@@ -1,76 +1,54 @@
|
||||
"""create tasks table
|
||||
|
||||
Revision ID: 0002
|
||||
Revises:
|
||||
Revises: 0001
|
||||
Create Date: 2025-01-01 00:00:00.000000
|
||||
"""
|
||||
|
||||
from alembic import op
|
||||
import sqlalchemy as sa
|
||||
from sqlalchemy.dialects.postgresql import ARRAY
|
||||
|
||||
revision = "0002"
|
||||
down_revision = None
|
||||
down_revision = "0001"
|
||||
branch_labels = None
|
||||
depends_on = None
|
||||
|
||||
|
||||
def upgrade() -> None:
|
||||
task_status = sa.Enum("todo", "in_progress", "done", name="task_status")
|
||||
task_status.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
task_priority = sa.Enum("none", "low", "medium", "high", name="task_priority")
|
||||
task_priority.create(op.get_bind(), checkfirst=True)
|
||||
|
||||
op.create_table(
|
||||
"tasks",
|
||||
sa.Column("id", sa.Integer(), primary_key=True),
|
||||
sa.Column("title", sa.Text(), nullable=False, server_default=""),
|
||||
sa.Column("description", sa.Text(), nullable=False, server_default=""),
|
||||
sa.Column(
|
||||
"status",
|
||||
task_status,
|
||||
nullable=False,
|
||||
server_default="todo",
|
||||
),
|
||||
sa.Column(
|
||||
"priority",
|
||||
task_priority,
|
||||
nullable=False,
|
||||
server_default="none",
|
||||
),
|
||||
sa.Column("due_date", sa.Date(), nullable=True),
|
||||
sa.Column(
|
||||
"note_id",
|
||||
sa.Integer(),
|
||||
sa.ForeignKey("notes.id", ondelete="SET NULL"),
|
||||
nullable=True,
|
||||
),
|
||||
sa.Column("tags", ARRAY(sa.Text()), nullable=False, server_default="{}"),
|
||||
sa.Column(
|
||||
"created_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
sa.Column(
|
||||
"updated_at",
|
||||
sa.DateTime(timezone=True),
|
||||
nullable=False,
|
||||
server_default=sa.func.now(),
|
||||
),
|
||||
)
|
||||
|
||||
op.create_index("ix_tasks_tags", "tasks", ["tags"], postgresql_using="gin")
|
||||
op.create_index("ix_tasks_note_id", "tasks", ["note_id"])
|
||||
op.create_index("ix_tasks_status", "tasks", ["status"])
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE task_status AS ENUM ('todo', 'in_progress', 'done');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$
|
||||
""")
|
||||
op.execute("""
|
||||
DO $$ BEGIN
|
||||
CREATE TYPE task_priority AS ENUM ('none', 'low', 'medium', 'high');
|
||||
EXCEPTION WHEN duplicate_object THEN null;
|
||||
END $$
|
||||
""")
|
||||
op.execute("""
|
||||
CREATE TABLE IF NOT EXISTS tasks (
|
||||
id SERIAL PRIMARY KEY,
|
||||
title TEXT NOT NULL DEFAULT '',
|
||||
description TEXT NOT NULL DEFAULT '',
|
||||
status task_status NOT NULL DEFAULT 'todo',
|
||||
priority task_priority NOT NULL DEFAULT 'none',
|
||||
due_date DATE,
|
||||
note_id INTEGER REFERENCES notes(id) ON DELETE SET NULL,
|
||||
tags TEXT[] NOT NULL DEFAULT '{}',
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
|
||||
)
|
||||
""")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_tasks_tags ON tasks USING gin (tags)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_tasks_note_id ON tasks (note_id)")
|
||||
op.execute("CREATE INDEX IF NOT EXISTS ix_tasks_status ON tasks (status)")
|
||||
|
||||
|
||||
def downgrade() -> None:
|
||||
op.drop_index("ix_tasks_status", table_name="tasks")
|
||||
op.drop_index("ix_tasks_note_id", table_name="tasks")
|
||||
op.drop_index("ix_tasks_tags", table_name="tasks")
|
||||
op.drop_table("tasks")
|
||||
|
||||
sa.Enum(name="task_priority").drop(op.get_bind(), checkfirst=True)
|
||||
sa.Enum(name="task_status").drop(op.get_bind(), checkfirst=True)
|
||||
op.execute("DROP INDEX IF EXISTS ix_tasks_status")
|
||||
op.execute("DROP INDEX IF EXISTS ix_tasks_note_id")
|
||||
op.execute("DROP INDEX IF EXISTS ix_tasks_tags")
|
||||
op.execute("DROP TABLE IF EXISTS tasks")
|
||||
op.execute("DROP TYPE IF EXISTS task_priority")
|
||||
op.execute("DROP TYPE IF EXISTS task_status")
|
||||
|
||||
Reference in New Issue
Block a user