From 5e62da2013bb4a8bf6c667bb1472013615565bf0 Mon Sep 17 00:00:00 2001 From: Kayos Date: Thu, 30 Apr 2026 11:53:34 -0700 Subject: [PATCH] Step 2 follow-up: use any usable Mealie token for the boot backfill System MEALIE_API_TOKEN is 401 (legacy 'Cauldron' token from Mealie mint long ago, since rotated/expired). The backfill tries it first; on 401 falls back to any stored per-user token via the new db.first_usable_mealie_token helper. Cobb's user is admin in Mealie and his token returns the full group catalog, so the backfill works without minting a fresh system token. --- cauldron/db.py | 17 +++++++++++++++++ cauldron/server.py | 23 +++++++++++++++++++---- 2 files changed, 36 insertions(+), 4 deletions(-) diff --git a/cauldron/db.py b/cauldron/db.py index 4f8c62c..d491696 100644 --- a/cauldron/db.py +++ b/cauldron/db.py @@ -375,6 +375,23 @@ class DB: # --- mealie token ops --------------------------------------------------- + def first_usable_mealie_token(self) -> dict | None: + """Return the freshest (last_validated DESC) per-user encrypted Mealie + token row. Used at boot as a fallback when the system MEALIE_API_TOKEN + env is dead — group admins can list foods, so any user's token works + for the backfill scan.""" + with self.conn() as c, c.cursor() as cur: + cur.execute( + """SELECT authentik_sub, encrypted_token + FROM cauldron_user_mealie_tokens + WHERE last_failure_at IS NULL OR + last_validated > COALESCE(last_failure_at, '1970-01-01') + ORDER BY last_validated DESC, created_at DESC + LIMIT 1""" + ) + row = cur.fetchone() + return dict(row) if row else None + def get_user_mealie_token_blob(self, sub: str) -> bytes | None: with self.conn() as c, c.cursor() as cur: cur.execute( diff --git a/cauldron/server.py b/cauldron/server.py index ba7e174..a5fd2ab 100644 --- a/cauldron/server.py +++ b/cauldron/server.py @@ -71,12 +71,27 @@ def create_app() -> Flask: # One-time backfill: re-key the legacy cauldron_foods (USDA + curated) # densities into cauldron_food_metadata, keyed by Mealie food.id. Runs - # only when the new metadata table is empty. After this lands, the - # legacy parallel-name catalog is no longer authoritative — Step 4 - # cleanup can drop cauldron_foods entirely. + # only when the new metadata table is empty. The system MEALIE_API_TOKEN + # may be expired (Cobb's "Cauldron" token was minted long ago); fall + # back to any stored per-user token since household admins can list + # all foods anyway. + def _resolve_backfill_mealie() -> "Mealie": + try: + system_mealie.who_am_i() + return system_mealie + except Exception: + stash = db.first_usable_mealie_token() + if not stash: + raise RuntimeError("no usable Mealie token (system + user both unavailable)") + return Mealie( + base_url=cfg.mealie_api_url, + api_token=crypto.decrypt(stash["encrypted_token"]), + ) + try: if foods.metadata_count(db) == 0: - stats = foods.backfill_seed_from_legacy(db, system_mealie) + mealie_for_backfill = _resolve_backfill_mealie() + stats = foods.backfill_seed_from_legacy(db, mealie_for_backfill) app.logger.info( "cauldron_food_metadata backfill: matched=%d missed=%d total_mealie=%d", stats.get("matched", 0),