Senior Technical Review Notes
This page explains the implementation decisions a reviewer should care about: security boundaries, database-level credit protection, error handling, and admin permissions.
API keys
AI and service-role keys are server-only and never use NEXT_PUBLIC prefixes.
Atomic credits
spend_credits_if_enough uses UPDATE WHERE credits >= amount to prevent double-spend.
AI failures
Provider errors, timeouts, and empty responses write failed logs and deduct zero credits.
Admin security
Admin role is checked in route handlers and again inside the recharge SQL function.
Product Flow
User registers with Supabase Auth.
PostgreSQL trigger creates profile with 1000 credits.
User submits prompt to POST /api/ai/chat.
Backend validates prompt and checks credits before provider call.
DeepSeek API is called only from the server through the OpenAI-compatible SDK.
Successful non-empty response calls spend_credits_if_enough.
Failed, timed out, or empty response deducts 0 credits.
Every completed AI attempt writes a usage_logs row.
Admin recharge is protected by route role checks and SQL role checks.
HTTP Behavior
Unauthenticated users cannot call protected APIs.
Invalid prompt or recharge input is rejected by Zod.
Low credits or normal-user admin API access is blocked.
Concurrent spend lost the atomic deduction race.
AI provider failed, timed out, or returned empty output.
Review These Files
app/api/ai/chat/route.tsMain billing flow and failed-call handling
supabase/schema.sqlRLS, triggers, transactions, atomic SQL functions
lib/ai.tsDeepSeek-first OpenAI-compatible client with AbortController timeout
lib/auth.tsUser and admin route guards
app/api/admin/recharge/route.tsAdmin-only credit recharge route
components/demo-lab.tsxPublic demo mode for reviewers without private keys
Interview Explanation
This demo is not just an AI chat page. It is a mini AI SaaS billing system. The core logic is that a user must have enough credits before an AI call, the AI key stays on the backend, successful calls deduct credits only after the provider returns a valid answer, failed calls deduct zero credits, and every completed attempt is logged.
The most important engineering decision is moving credit mutation into PostgreSQL. The app calls spend_credits_if_enough, which performs one guarded update. PostgreSQL row-level locking handles concurrent requests, so two racing calls cannot spend the same credits.
The admin recharge path is also protected twice: first by the Next.js route handler and again by the admin_add_credits database function. A normal user receives 403 even if they call the admin API directly.