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.
This commit is contained in:
Kayos 2026-04-30 11:53:34 -07:00
parent f74a627ac7
commit 5e62da2013
2 changed files with 36 additions and 4 deletions

View file

@ -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(

View file

@ -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),