"""Runtime profile overrides persisted in PostgreSQL.
Currently stores only `is_admin_only` per profile. Base config comes from
JSON files on disk; this layer applies runtime changes made via the admin API.
"""
from datetime import datetime, timezone
import asyncpg
import structlog
log = structlog.get_logger()
_DDL = """
CREATE TABLE IF NOT EXISTS profile_overrides (
profile_id TEXT PRIMARY KEY,
is_admin_only BOOLEAN NOT NULL DEFAULT FALSE,
updated_at TIMESTAMPTZ NOT NULL
)
"""
async def ensure_table(pool: asyncpg.Pool) -> None:
async with pool.acquire() as conn:
await conn.execute(_DDL)
async def load_overrides(pool: asyncpg.Pool) -> dict[str, bool]:
"""Return mapping profile_id -> is_admin_only from DB."""
async with pool.acquire() as conn:
rows = await conn.fetch("SELECT profile_id, is_admin_only FROM profile_overrides")
return {r["profile_id"]: r["is_admin_only"] for r in rows}
async def save_override(
pool: asyncpg.Pool, profile_id: str, is_admin_only: bool
) -> None:
now = datetime.now(timezone.utc)
async with pool.acquire() as conn:
await conn.execute(
"""INSERT INTO profile_overrides (profile_id, is_admin_only, updated_at)
VALUES ($1, $2, $3)
ON CONFLICT (profile_id) DO UPDATE SET
is_admin_only = EXCLUDED.is_admin_only,
updated_at = EXCLUDED.updated_at""",
profile_id,
is_admin_only,
now,
)
log.info("profile.override_saved", profile_id=profile_id, is_admin_only=is_admin_only)