restaurants.cover_url existed in the schema and was already read by the
public menu page, but nothing could ever write it — the update endpoint
didn't accept it and there was no picker in the app. Adds coverUrl to
PATCH /restaurants/:id and a cover photo picker in the mobile account
screen, next to the existing logo picker.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds enabled_languages on restaurants and translations JSONB on menu
categories/items. New /restaurants/:id/languages endpoints bulk-translate
the full menu via Gemini/GPT-4o-mini (reusing the ai-scanner dual-provider
pattern) when a language is enabled. New categories/items are auto-
translated in the background into any already-enabled languages.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Verifies the shared-secret Authorization header, then maps
RevenueCat's app_user_id (the restaurant id — mobile must configure
Purchases with appUserID: restaurantId) to an upsert into
subscriptions. INITIAL_PURCHASE/RENEWAL/PRODUCT_CHANGE/UNCANCELLATION
-> active, BILLING_ISSUE -> grace_period, EXPIRATION -> suspended.
CANCELLATION alone does not suspend — it only means auto-renew is
off, access continues until the period actually expires.
Tested end-to-end against the real Supabase project (create
restaurant, POST the webhook, verify the subscriptions row).
Removes the per-item confidence field end to end (prompt, extraction
parsing, ai_import_items writes, API responses, low-confidence badge
in the mobile review screen). ai_import_items.confidence column is
left in place but unused (nullable, no destructive migration).
Adding a custom domain now:
- creates a Cloudflare Custom Hostname (SSL for SaaS) automatically,
returning the ownership + SSL validation TXT records to show the
restaurant owner alongside the CNAME instruction
- adds the domain to the Coolify app's fqdn and triggers a restart
(Coolify only regenerates Traefik labels on deploy, not on a bare
fqdn PATCH — coollabsio/coolify#6281)
- /verify now checks Cloudflare's actual ssl.status instead of doing
a DNS CNAME lookup, which is structurally blind on proxied hostnames
Tested end-to-end against the real Cloudflare zone and Coolify app
(create, verify data, then clean up) before this push — see
docs/PROGRESS.md.
Requires CLOUDFLARE_API_TOKEN, CLOUDFLARE_ZONE_ID, COOLIFY_API_TOKEN,
COOLIFY_BASE_URL, COOLIFY_WEB_APP_UUID in the API's environment.
Without them, domain add/verify falls back to DB-only bookkeeping.
- domains.ts: a proxied Cloudflare custom hostname's real CNAME target
is never visible to a plain DNS lookup, so a re-verify always fails
and was silently downgrading already-verified domains back to
pending. Only a first-time check can now land on pending.
- restaurants.ts: PUT /restaurants/:id/theme referenced an undeclared
themeData variable (pre-existing tsc error on main, not introduced
here) — added the missing themes-by-key lookup it depended on.
Default 1 MiB limit rejected base64-encoded camera photos before the
request handler ever ran ("Request body is too large"), unrelated to
the OpenAI model. Also adds the public domains RLS policy needed for
custom-domain resolution on the public menu page (already applied
manually via SQL Editor, committing for history).