diff --git a/backend/src/cyclone/auth/bootstrap.py b/backend/src/cyclone/auth/bootstrap.py index ca8f72a..3a9b036 100644 --- a/backend/src/cyclone/auth/bootstrap.py +++ b/backend/src/cyclone/auth/bootstrap.py @@ -45,6 +45,16 @@ def run() -> None: username = os.environ.get("CYCLONE_ADMIN_USERNAME") password = os.environ.get("CYCLONE_ADMIN_PASSWORD") + # First-boot fix: ``python -m cyclone`` calls bootstrap before any + # subcommand or the FastAPI lifespan handler runs, so on a brand-new + # DB ``SessionLocal()`` raises "init_db() has not been called". + # Initialize here so ``serve``, ``users create``, and friends can + # all reach the DB without the operator having to know about + # migrations. Idempotent — no-op when the schema is already current. + from cyclone import db as _db + + _db.init_db() + with SessionLocal()() as db: existing = db.execute(select(User)).scalars().first() if existing is not None: diff --git a/backend/src/cyclone/auth/permissions.py b/backend/src/cyclone/auth/permissions.py index c0f01c8..0fec752 100644 --- a/backend/src/cyclone/auth/permissions.py +++ b/backend/src/cyclone/auth/permissions.py @@ -41,6 +41,7 @@ PERMISSIONS: dict[tuple[str, str], set[Role]] = { ("GET", "/api/dashboard/summary"): ALL_ROLES, ("GET", "/api/activity"): ALL_ROLES, ("GET", "/api/inbox/lanes"): ALL_ROLES, + ("GET", "/api/inbox/export.csv"): ALL_ROLES, ("GET", "/api/reconcile"): ALL_ROLES, ("GET", "/api/audit-log"): ADMIN_ONLY, @@ -48,6 +49,9 @@ PERMISSIONS: dict[tuple[str, str], set[Role]] = { ("POST", "/api/parse-837"): WRITE_ROLES, ("POST", "/api/parse-835"): WRITE_ROLES, ("POST", "/api/inbox"): WRITE_ROLES, + ("POST", "/api/inbox/candidates"): WRITE_ROLES, + ("POST", "/api/inbox/rejected"): WRITE_ROLES, + ("POST", "/api/inbox/payer-rejected"): WRITE_ROLES, ("POST", "/api/reconcile"): WRITE_ROLES, ("POST", "/api/resubmit"): WRITE_ROLES, ("POST", "/api/acks"): WRITE_ROLES,