rgboards/rg-boards
Lanes, team task boards you run from the web or from Claude/ChatGPT (MCP). FastAPI + DataK3 + appid + Dodil Models.
A remix template · 0 remixes · Remix rgboards/rg-boards
app/boards/infrastructure/migrations.py
"""Additive start-up migrations: create missing tables, add missing columns. Never renames or
drops. DataK3 refuses CREATE TABLE IF NOT EXISTS when the existing table has other columns,
so a table that exists only gets the columns it lacks, as explicit ALTERs.
The schema lives in schema.sql at the repo root (the remix applies it to a new bucket); this
module embeds the same statements so an existing bucket catches up on deploy.
"""
import logging
import re
log = logging.getLogger("boards.migrations")
SCHEMA = """
CREATE TABLE IF NOT EXISTS boards (id TEXT PRIMARY KEY, title TEXT NOT NULL,
owner_email TEXT NOT NULL, created_at TIMESTAMP, lists TEXT);
CREATE TABLE IF NOT EXISTS memberships (id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
email TEXT NOT NULL, role TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS cards (id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
list_name TEXT NOT NULL, title TEXT NOT NULL, assignee TEXT, due_date DATE,
updated_at TIMESTAMP, number BIGINT, description TEXT, labels TEXT, position DOUBLE,
version BIGINT, archived BOOLEAN, created_by TEXT, created_at TIMESTAMP);
CREATE TABLE IF NOT EXISTS activity (id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
card_id TEXT, actor TEXT NOT NULL, action TEXT NOT NULL, event_at TIMESTAMP,
field TEXT, old_value TEXT, new_value TEXT);
CREATE TABLE IF NOT EXISTS card_links (id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
card_id TEXT NOT NULL, blocked_by TEXT NOT NULL, created_by TEXT, created_at TIMESTAMP);
CREATE TABLE IF NOT EXISTS checklist_items (id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
card_id TEXT NOT NULL, text TEXT NOT NULL, done BOOLEAN, position BIGINT);
CREATE TABLE IF NOT EXISTS card_vectors (card_id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
text_hash TEXT NOT NULL, vector TEXT NOT NULL);
CREATE TABLE IF NOT EXISTS board_summaries (id TEXT PRIMARY KEY, board_id TEXT NOT NULL,
day TEXT NOT NULL, text TEXT, written_at TIMESTAMP);
"""
def _columns(statement: str) -> tuple:
table = re.search(r"EXISTS\s+(\w+)", statement).group(1)
body = statement[statement.index("(") + 1:statement.rindex(")")]
cols = []
for part in body.split(","):
bits = part.split()
if len(bits) >= 2:
cols.append((bits[0], bits[1]))
return table, cols
def migrate(conn) -> None:
tables = {r[0] for r in conn.execute(
"SELECT table_name FROM information_schema.tables").fetchall()}
for stmt in [s.strip() for s in SCHEMA.split(";") if s.strip()]:
table, cols = _columns(stmt)
if table not in tables:
log.info("creating %s", table)
conn.execute(stmt)
continue
have = {r[0] for r in conn.execute(
"SELECT column_name FROM information_schema.columns WHERE table_name = %s",
(table,)).fetchall()}
for name, typ in cols:
if name not in have:
log.info("adding %s.%s", table, name)
conn.execute(f"ALTER TABLE {table} ADD COLUMN {name} {typ}")