From 6ee0822a966f2ac31a02469725e716b942051db7 Mon Sep 17 00:00:00 2001 From: "reza.g" Date: Mon, 21 Sep 2026 20:06:28 +0330 Subject: [PATCH] feat(drafts): add bot_drafts schema, automatic table bootstrap, and sync REST endpoints --- .gitignore | 2 + backend/app/db/init_db.py | 30 +++++++++ backend/app/main.py | 5 ++ backend/app/models/draft.py | 20 ++++++ backend/app/routers/__init__.py | 2 + backend/app/routers/drafts.py | 106 ++++++++++++++++++++++++++++++++ sql/migration.sql | 18 ++++++ 7 files changed, 183 insertions(+) create mode 100644 backend/app/models/draft.py create mode 100644 backend/app/routers/drafts.py diff --git a/.gitignore b/.gitignore index 8f32c1d..b14be21 100644 --- a/.gitignore +++ b/.gitignore @@ -26,6 +26,8 @@ frontend/.env # ---- Local session / backups ---- .zcode/ +.zcodeignore +scratch/ v1.2 - Copy/ inv dash/ diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py index 018ff34..c59a345 100644 --- a/backend/app/db/init_db.py +++ b/backend/app/db/init_db.py @@ -432,3 +432,33 @@ async def ensure_audit_table(engine: AsyncEngine) -> None: for stmt in indexes: await conn.execute(text(stmt)) logger.info("audit_log table ensured") + + +async def ensure_bot_drafts_table(engine: AsyncEngine) -> None: + """Ensure bot_drafts table and indexes exist for Bale bot quote sync.""" + ddl = """ + CREATE TABLE IF NOT EXISTS bot_drafts ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + chat_id BIGINT NOT NULL, + kind VARCHAR(16) NOT NULL, + payload JSONB NOT NULL DEFAULT '{}'::jsonb, + missing JSONB, + status VARCHAR(16) NOT NULL DEFAULT 'active', + source VARCHAR(16), + review_message_id BIGINT, + last_bale_update_at TIMESTAMPTZ, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + updated_at TIMESTAMPTZ NOT NULL DEFAULT now() + ) + """ + indexes = ( + "CREATE INDEX IF NOT EXISTS ix_bot_drafts_chat_id ON bot_drafts(chat_id)", + "CREATE INDEX IF NOT EXISTS ix_bot_drafts_chat_status ON bot_drafts(chat_id, status)", + "ALTER TABLE bot_drafts ADD COLUMN IF NOT EXISTS last_bale_update_at TIMESTAMPTZ", + ) + async with engine.begin() as conn: + await conn.execute(text(ddl)) + for stmt in indexes: + await conn.execute(text(stmt)) + logger.info("bot_drafts table ensured") + diff --git a/backend/app/main.py b/backend/app/main.py index adf54bb..94cba30 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -27,6 +27,7 @@ from app.db.engine import AsyncSessionLocal, engine from app.db.init_db import ( ensure_attachment_table, ensure_audit_table, + ensure_bot_drafts_table, ensure_drop_category_tables, ensure_dashboard_tables, ensure_material_topic_column, @@ -107,6 +108,7 @@ async def lifespan(_app: FastAPI): await ensure_v26(engine) await ensure_audit_table(engine) await ensure_drop_category_tables(engine) + await ensure_bot_drafts_table(engine) task = asyncio.create_task(_auto_expire_loop()) if settings.AUTO_EXPIRE_ENABLED else None try: yield @@ -233,12 +235,15 @@ from app.routers.auth import router as _auth_router # noqa: E402 # attach an Authorization header to