This project now supports routing AI calls through a backend proxy.
GEMINI_API_KEY on server side.supabase/functions/ai-proxy/index.tssupabase/migrations/004_ai_usage_guard.sqllib/services/ai_backend_proxy.dartsupabase db push
supabase secrets set GEMINI_API_KEY=your_key
supabase secrets set GEMINI_MODEL=gemini-3-flash-preview
supabase secrets set GEMINI_FALLBACK_MODEL=gemini-2.0-flash
supabase secrets set GOOGLE_SERVICE_ACCOUNT_JSON='{"type":"service_account",...}'
supabase functions deploy ai-proxy
flutter run --dart-define=AI_BACKEND_PROXY_URL=https://<project-ref>.supabase.co/functions/v1/ai-proxy
Optional static bearer:
flutter run --dart-define=AI_BACKEND_PROXY_BEARER=your_token
To enforce proxy-only mode (recommended for production):
flutter run \
--dart-define=AI_BACKEND_PROXY_URL=https://<project-ref>.supabase.co/functions/v1/ai-proxy \
--dart-define=AI_PROXY_REQUIRED=true
The client calls these paths under AI_BACKEND_PROXY_URL:
POST /analyze-pantryPOST /process-receiptPOST /generate-from-ingredientsPOST /translate-recipePOST /localize-pantryPOST /activate-proEach successful response must be:
{
"data": { }
}
guard_ai_usage currently enforces (server-side):
6/day, 10s cooldown3/day, 10s cooldown6/day, 10s cooldown3/day, 5s cooldownAdjust these constants in supabase/functions/ai-proxy/index.ts as needed.
The proxy now reads public.user_subscription_tiers:
free (default)proYou can promote a user with SQL:
insert into public.user_subscription_tiers (user_id, tier)
values ('<auth-user-uuid>', 'pro')
on conflict (user_id)
do update set tier = excluded.tier, updated_at = now();
Purchase events are recorded in public.pro_purchase_events.
When app purchase succeeds, client calls POST /activate-pro.
For Android, function verifies purchase using Google Play Developer API
(purchases.subscriptionsv2.get) before upgrading tier.
Required request payload for Android:
{
"source": "iap",
"platform": "android",
"productId": "your.pro.subscription.id",
"packageName": "com.cyberchef.pantry",
"purchaseToken": "play_purchase_token",
"purchaseId": "optional-order-id"
}
If verification fails, endpoint returns 402 purchase_not_verified and
does not upgrade the user.