Back home

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.

Open demo lab

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

1

User registers with Supabase Auth.

2

PostgreSQL trigger creates profile with 1000 credits.

3

User submits prompt to POST /api/ai/chat.

4

Backend validates prompt and checks credits before provider call.

5

DeepSeek API is called only from the server through the OpenAI-compatible SDK.

6

Successful non-empty response calls spend_credits_if_enough.

7

Failed, timed out, or empty response deducts 0 credits.

8

Every completed AI attempt writes a usage_logs row.

9

Admin recharge is protected by route role checks and SQL role checks.

HTTP Behavior

401

Unauthenticated users cannot call protected APIs.

400

Invalid prompt or recharge input is rejected by Zod.

403

Low credits or normal-user admin API access is blocked.

409

Concurrent spend lost the atomic deduction race.

500

AI provider failed, timed out, or returned empty output.

Review These Files

app/api/ai/chat/route.ts

Main billing flow and failed-call handling

supabase/schema.sql

RLS, triggers, transactions, atomic SQL functions

lib/ai.ts

DeepSeek-first OpenAI-compatible client with AbortController timeout

lib/auth.ts

User and admin route guards

app/api/admin/recharge/route.ts

Admin-only credit recharge route

components/demo-lab.tsx

Public 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.