diff --git a/backend/app/db/init_db.py b/backend/app/db/init_db.py index 14b96a2..062973e 100644 --- a/backend/app/db/init_db.py +++ b/backend/app/db/init_db.py @@ -414,6 +414,50 @@ async def ensure_drop_category_tables(engine: AsyncEngine) -> None: logger.info("category taxonomy tables dropped (attributes / ref units / conversions)") +async def ensure_rbac_schema(engine: AsyncEngine) -> None: + """Panel-access schema (pm branch): users.username, projects planning + columns, user_panels and user_project_members. Idempotent; safe on any + volume — the RBAC model expects these and login 500s without them.""" + stmts = ( + "ALTER TABLE users ADD COLUMN IF NOT EXISTS username TEXT", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS location TEXT DEFAULT ''", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS project_manager TEXT DEFAULT ''", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS planned_start DATE", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS planned_end DATE", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS actual_start DATE", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS actual_end DATE", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS status TEXT DEFAULT 'ACTIVE'", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS progress DOUBLE PRECISION DEFAULT 0.0", + "ALTER TABLE projects ADD COLUMN IF NOT EXISTS updated_at TIMESTAMPTZ DEFAULT now()", + """ + CREATE TABLE IF NOT EXISTS user_panels ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + panel TEXT NOT NULL, + level TEXT NOT NULL, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_user_panel UNIQUE (user_id, panel) + ) + """, + "CREATE INDEX IF NOT EXISTS ix_user_panels_user_id ON user_panels(user_id)", + """ + CREATE TABLE IF NOT EXISTS user_project_members ( + id UUID PRIMARY KEY DEFAULT gen_random_uuid(), + user_id UUID NOT NULL REFERENCES users(id) ON DELETE CASCADE, + project_id UUID NOT NULL REFERENCES projects(id) ON DELETE CASCADE, + created_at TIMESTAMPTZ NOT NULL DEFAULT now(), + CONSTRAINT uq_user_project_member UNIQUE (user_id, project_id) + ) + """, + "CREATE INDEX IF NOT EXISTS ix_user_project_members_user_id ON user_project_members(user_id)", + "CREATE INDEX IF NOT EXISTS ix_user_project_members_project_id ON user_project_members(project_id)", + ) + async with engine.begin() as conn: + for stmt in stmts: + await conn.execute(text(stmt)) + logger.info("RBAC schema ensured (username / project columns / user_panels / user_project_members)") + + async def ensure_audit_table(engine: AsyncEngine) -> None: """v2.7: general activity trail (audit_log). Idempotent; safe on any volume.""" ddl = """ diff --git a/backend/app/main.py b/backend/app/main.py index aa86a08..be34a9d 100644 --- a/backend/app/main.py +++ b/backend/app/main.py @@ -32,6 +32,7 @@ from app.db.init_db import ( ensure_dashboard_tables, ensure_material_topic_column, ensure_quote_projects, + ensure_rbac_schema, ensure_project_transfers_table, ensure_v26, ) @@ -107,6 +108,7 @@ async def lifespan(_app: FastAPI): await ensure_material_topic_column(engine) await ensure_quote_projects(engine) await ensure_v26(engine) + await ensure_rbac_schema(engine) await ensure_audit_table(engine) await ensure_drop_category_tables(engine) await ensure_bot_drafts_table(engine)