Digital storefront for subscription credentials (streaming accounts, top-ups, and similar goods). Buyers browse a public catalog, check out with manual payment verification, and receive AES-256-GCM encrypted credentials in their dashboard. Operators run the whole shop from a concealed admin panel: order queue, vault stock, analytics, settings, and a tiered Danger Zone for destructive actions.
Live: storefront https://stzen.web.id · API https://api.stzen.web.id
Staging: https://dev.stzen.web.id
| Layer | Choice |
|---|---|
| API | Hono 4 on Cloudflare Workers (store-subs-api) |
| Storefront + admin SPA | React 19 + Vite 6 + DaisyUI 5 on Cloudflare Pages |
| Database + Auth | Supabase Postgres + Supabase Auth (JWT, JWKS verification) |
| ORM / migrations | Drizzle ORM (drizzle-kit migrate, forward-only) |
| Language | TypeScript strict, end-to-end types via hono/client (AppType) |
Modular monolith. The server composes self-contained feature modules; modules
may only import from shared/ (DB, middleware, errors, crypto) and talk to
each other through public service APIs. Route handlers stay thin and delegate
to services, which throw typed AppErrors handled by one global handler.
server/
app.ts createApp() factory + AppType export
index.ts local entry (tsx); workers-entry.ts for Workers
shared/ db · middleware/auth · errors · lib (crypto, audit)
modules/ products · orders · checkout · vault · admin ·
payments · danger
db/ drizzle schema · migrations · rls-verify.sql
scripts/ check-secrets.mjs · lint-migrations.mjs
client/src/
pages/ Catalog · ProductDetail · Dashboard · admin/*
features/products/ admin product/variant/stock management
lib/ hono client · supabase · receipt printer
API routes are versioned (/api/v1/*); /api/health and
/api/settings/public are unversioned infrastructure endpoints.
Prerequisites: Node 20+, npm, a Supabase project, Wrangler authenticated
(wrangler login) for Worker deploys.
npm install
# 1. Environment files (all gitignored; .example files show the shape)
cp server/.dev.vars.example server/.dev.vars # wrangler dev bindings
# then fill: server/.env, server/.dev.vars, client/.env
# 2. Database
npm run db:migrate --workspace=server # or db:push for iteration
# 3. Run (three terminals, or npm run dev for server+client)
npm run dev:server # tsx on :3000 (fast iteration)
npm run dev:workers --workspace=server # wrangler dev on :8787 (real Workers runtime)
npm run dev:client # vite on :5173, /api proxied to the APIUse tsx for speed, wrangler dev for truth: anything bound for staging gets
its final check under the real Workers runtime. LAN phone testing works out
of the box (same-origin /api + dev-only LAN CORS).
| File / store | Keys |
|---|---|
server/wrangler.jsonc vars (committed) |
SUPABASE_URL, SUPABASE_ANON_KEY (publishable, public by design), ENV, CORS_ALLOWED_ORIGINS, PAYMENT_* non-secret config |
| Wrangler secret store (per env) | SUPABASE_SERVICE_ROLE_KEY, AES_SECRET_KEY, provider keys |
client/.env + Pages env |
VITE_SUPABASE_URL, VITE_SUPABASE_ANON_KEY, VITE_API_BASE_URL, VITE_WHATSAPP_NUMBER |
npm run deploy --workspace=server pins --env production and runs
check-secrets.mjs first (required secrets present, production CORS free of
dev origins). Never bare wrangler deploy.
workflow_dispatchstaging deploy → smoke ondev.stzen.web.id.- Merge to
main→ CI typechecks, lints migrations, deploys the Worker to production. Pages rebuilds the storefront on push. - Migrations are manual-only (
workflow_dispatch), forward-only. - Rollback = CI re-run on the prior commit.
- Credentials encrypted at rest (AES-256-GCM in
vault_items), decrypted only inside authenticated handlers for the order owner. The AES key is never rotated (rotation = vault loss) and never committed. - Atomic allocation:
allocate_credential()RPC withFOR UPDATE SKIP LOCKED, never allocate in JS memory. - RLS: profiles/products/orders scoped to owners, catalog public for
active rows,
vault_items/audit_logs/settings/idempotency_claimsserver-only (RLS on, zero policies). Verify anytime: runserver/db/rls-verify.sqlin the SQL editor. - Concealed admin:
/api/v1/admin/*and/admin*return 404 (not 401/403) for missing, bad, or wrong-role tokens. - Danger Zone (admin settings): tiered destructive ops with preview-first, typed-phrase confirm, password re-auth, export-gated purge, everything audited. Catalog deletes are deactivate-first; orders are never deleted (10-year bookkeeping retention under Indonesia UU KUP Pasal 28).
- Rate limits: strict bucket on
/admin/danger/*, coverage on public reads, in-memory per isolate (edge rules are the growth trigger).
| Command | What |
|---|---|
npm run dev |
tsx API + vite client |
npm run deploy --workspace=server |
guarded production Worker deploy |
npm run lint:migrations --workspace=server |
offline destructive-SQL lint |
npm test --workspace=server |
unit tests |
Conventions: conventional commits (feat(server): …), docs live in docs/,
AGENTS.md holds the full coding contract.
GNU Affero General Public License v3.0 - see LICENSE. Hosted or distributed modifications must share source under the same terms.