Newer
Older
navi-1 / navi / profiles / _overrides.py
"""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)