Team Desk.
A franchise network’s whole operation in one system, with each person seeing only what is theirs.


The problem
Missing metrics · Rework
A franchisor has to standardize many units and know what is happening in each one, without spreadsheets or WhatsApp groups. Owners, consultants, franchisees, managers and staff all see and do different things, and nothing can leak from one unit to another.
What I built
I built the whole product, from PRD to deploy. SLAs count business hours and escalate on their own. An inspection submitted more than 200 meters from the unit goes to review. Every "non-compliant" item becomes a 5W2H action plan due in 7 days. An approved lead becomes a unit, with its chat and rollout project created in the same transaction. The schedule recalculates its critical path every night, and the corporate university issues certificates with a public verification page.
Technical decisions
01An audit log the app itself cannot erase
The audit table is partitioned by month, and the database role the app uses has no UPDATE, DELETE or TRUNCATE on it. A bug, or someone with access to the app, cannot rewrite history.
Where it lives in the code: drizzle/0002_audit_partition_grants.sql
02Signed webhooks with SSRF protection
Every delivery carries an HMAC-SHA256 over the timestamp and raw body, retries 6 times with backoff and disables the endpoint after 10 failures. Before calling a customer URL, its own DNS lookup refuses private network ranges.
Where it lives in the code: lib/webhooks/signature.ts · url.ts · deliver.ts
03A public API that survives retries
Every POST requires an Idempotency-Key, pagination uses cursors, the limit is 300 requests per minute and the contract ships as OpenAPI 3.0. A client that repeats a call never creates anything twice.
Where it lives in the code: lib/api/idempotency.ts · openapi.ts
04Three processes, one domain
Web (Next), workers (BullMQ) and realtime (Socket.io with a Redis adapter) run as separate processes over the same domain code. A heavy job never freezes anyone’s screen.
05Tests against a real database
About 2,050 Vitest tests and 85 end-to-end tests. The integration ones run against real Postgres and Redis, and CI also checks that migrations match the schema. Uploads verify the file’s magic bytes, and rich text is stored as validated JSON, never as HTML.
Numbers
552
commits, single author
~2k
Vitest tests
85
end-to-end tests
61
tables
55
pages
13
webhook events
Stack
- Next.js 15
- React 19
- TypeScript
- Tailwind 4
- shadcn/ui
- Better Auth
- PostgreSQL 16
- Drizzle
- Redis
- BullMQ
- Socket.io
- S3
- Vitest
- Playwright
- Docker
Screens



Finance Web