Documentation: English · Tiếng Việt · 日本語
FoodFlow is a multi-tenant food-delivery learning project with a NestJS API, Admin and Restaurant dashboards, and Flutter customer/driver applications. Its deployment design uses Supabase for PostgreSQL/PostGIS, Realtime, and Storage; Railway for the API, worker, and Redis; and Vercel for the Admin and Restaurant dashboards. Docker Compose keeps a separate Socket.IO/Redis/MinIO compatibility profile for local development and self-hosting.
Live Admin: food-delivery-app-one-liard.vercel.app
This is the deployed sign-in surface. Public demo credentials are not documented; review the screenshots or run the seeded local stack for authenticated workflows.
See the authoritative production current-state record for the tag-bound release contract, live health checks, immutable image evidence, credential-rotation record, and rollback steps. Deployment IDs and commit hashes in dated sections are snapshots, not timeless current-state claims.
The 2026-07-17 recovery snapshot proved Railway health/readiness, all 42/42 active migrations, Database, Redis, Supabase Storage, private Broadcast authorization, and a zero-residue synthetic GPS/PostGIS flow after Supabase credentials were rotated and synchronized. That snapshot was not a unified web release: Admin reported e6def517… while Restaurant still reported 977d55f…. Release v0.1.4 is valid only when git rev-list -n 1 v0.1.4 equals origin/master, Railway API health and worker image metadata resolve to that SHA, and the Vercel Admin/Restaurant health endpoints return the same full SHA. Physical Android/iOS, controlled FCM, active-order routing, and optional provider integrations remain explicitly outside certification unless separately evidenced.
FoodFlow has four distinct product surfaces. Choose the Admin, Restaurant, Customer, or Driver guide, then see the full product gallery and mobile overview. The manifest records source heads, runtimes, capture times, privacy boundaries, and SHA-256 for every curated asset. Most web media came from Google Chrome against the isolated local E2E stack; two separately labelled Admin/Restaurant images are historical controlled-production evidence from SHA 17584153. Mobile media came from Flutter debug APKs on an Android API 35 x86_64 AVD; one separately labelled Driver recovery image is bounded production-emulator evidence. None is physical-device or app-store certification.
| Surface | Product runtime | Current visual evidence | How to review the product |
|---|---|---|---|
| Admin | Next.js web dashboard | 10 local PNGs, one GIF, one historical production PNG | Read the Admin guide, then run the Admin web app. |
| Restaurant | Next.js web dashboard | 10 local PNGs, one GIF, one historical production PNG | Read the Restaurant guide, then run the Restaurant web app. |
| Customer | Flutter/Riverpod Android/iOS app | Nine privacy-reviewed local WebPs and two GIFs | Read the Customer guide, then launch main_customer.dart on a device/emulator. |
| Driver | Flutter/Riverpod Android/iOS app | Eight Driver WebPs, two Android notification WebPs, and one GIF | Read the Driver guide, then launch main_driver.dart. |
The mobile captures use simulated GPS. Most use the isolated local stack; the manifest separately records the privacy-reviewed production-emulator recovery smoke. The latest local capture also covers Customer discovery → menu → cart → checkout → tracking and a realtime Driver dispatch offer. Because each was captured from a dirty workspace, authentic release media still requires a clean-head recapture from the chosen release candidate.
| Flow | Preview |
|---|---|
| Admin sign-in to overview | ![]() |
| Restaurant orders to menu | ![]() |
| Customer sign-in → registration → sign-in | ![]() |
| Customer Home → Orders → Profile | ![]() |
| Driver sign-in → Home → earnings → profile | ![]() |
| Surface | Source | Runtime | Local target | Primary guide |
|---|---|---|---|---|
| Backend API | backend/ |
NestJS 11, Prisma 6 | http://localhost:3001/api |
— |
| Admin | web/apps/admin/ |
Next.js 15, React 18, next-intl | http://localhost:3000 |
Admin guide |
| Restaurant | web/apps/restaurant/ |
Next.js 15, React 18, next-intl | http://localhost:3002 |
Restaurant guide |
| Customer | main_customer.dart |
Flutter/Riverpod native mobile app (Android/iOS) | device or emulator; Android customer flavor |
Customer guide |
| Driver | main_driver.dart |
Flutter/Riverpod native mobile app (Android/iOS) | device or emulator; Android driver flavor |
Driver guide |
Admin and Restaurant routes are locale-prefixed for vi, en, and ja. The API uses a shared success envelope ({ success: true, data, meta? }) and RFC 7807 Problem Details for errors.
Customer and Driver have no local web URLs. Use their explicit Flutter entrypoints; the --flavor commands below select the Android product flavors.
- Customer ordering, carts, addresses, vouchers, wallet/COD/SePay, reviews, support, and AI assistance.
- Driver online state, dispatch offers, fresh GPS validation, route guidance, ETA, heatmaps, earnings, KYC, and incentives.
- Restaurant order kanban, menu/options, promotions, revenue, reviews, notifications, staff, opening hours, and insights.
- Admin KPIs, orders, restaurants, users, drivers, live maps, promotions, audit, support, export, and AI telemetry.
- Tenant-scoped authorization for restaurant staff, realtime channels, tracking, exports, and administrative resources.
- Keyless MapLibre/OpenFreeMap basemaps plus backend Google Directions/owned OSRM routing when configured; route snapshots and telemetry fail closed instead of inventing coordinates, polylines, or ETA.
- DeepSeek-backed support through the backend adapter, with fail-closed configuration/provider errors, persisted usage telemetry, and no embedded provider key.
Google Maps is not required to boot FoodFlow. When neither Google Directions nor an owned OSRM service is configured, route calculation returns 503 DIRECTIONS_PROVIDER_NOT_CONFIGURED; the API and worker remain healthy. Railway currently runs with RAG_ENABLED=false because no DeepSeek credential is configured.
Flutter customer/driver ─┐
Admin + Restaurant web ──┼── HTTPS ──> NestJS API on Railway
│ │
│ ├── Supabase PostgreSQL/PostGIS
│ ├── Supabase private Broadcast + scoped JWT
│ ├── Supabase Storage
│ ├── Railway managed Redis
│ └── Postgres job outbox + worker
└── authorized Supabase channel subscriptions
Local/self-hosted compatibility: PostgreSQL + Socket.IO + Redis/BullMQ + MinIO
Provider selection is explicit:
| Concern | Managed production | Local/self-hosted compatibility |
|---|---|---|
| Database | Supabase PostgreSQL/PostGIS | PostGIS container |
| Realtime | REALTIME_PROVIDER=supabase |
socketio |
| Storage | STORAGE_PROVIDER=supabase |
minio |
| Queue | QUEUE_PROVIDER=supabase-postgres |
bullmq |
Admin, Restaurant, Customer, and Driver clients obtain short-lived, tenant-scoped realtime credentials from POST /api/realtime/token in managed mode. Mobile publishes GPS and dispatch decisions through authenticated REST and receives only allow-listed private Supabase Broadcast events; Socket.IO remains an explicit local/self-hosted compatibility provider.
| Artifact | Verified SHA digest | Aliases on Docker Hub and GHCR |
|---|---|---|
foodflow-backend |
sha256:09bae57f907fc6d13c9874a673a8d73397510e3d50f75b6f20415e948285c24e |
sha-84eeac3a2845868fc3a7fd45f8a73775e834a09d |
foodflow-migrate |
sha256:04a089f17269d8ceb94f3f55cb241c91e0eb16db68ffaae4067c8f9a7bbbe16d |
sha-84eeac3a2845868fc3a7fd45f8a73775e834a09d |
foodflow-admin |
sha256:1f75f3fd4cd6b9cc4b0814efee3aab79643f5f9ce6962cabd1505ef57c4992db |
sha-84eeac3a2845868fc3a7fd45f8a73775e834a09d |
foodflow-restaurant |
sha256:d92f6b8baaccc0a7ae8f83a22bff4d5d949fa07f6242fa456616465b44059316 |
sha-84eeac3a2845868fc3a7fd45f8a73775e834a09d |
All four Docker Hub and public GHCR SHA packages are public. Docker Publish run 29515529360 built and scanned these four immutable manifests; latest and semver aliases are intentionally unchanged until the remaining current-role/device evidence is certified.
Historical Docker candidate — superseded
The table below is retained as historical evidence for superseded runtime candidate f2c02ed76fb6a79671c1c51d10d8b6aef0f55b8b; do not use it for a new deployment. The current immutable Docker manifests are recorded in Immutable Docker artifacts — SHA 84eeac3.
| Artifact | SHA tag | Matching remote digest |
|---|---|---|
| API + worker | nguyenson1710/foodflow-backend:sha-f2c02ed76fb6a79671c1c51d10d8b6aef0f55b8b |
sha256:4c00f02f6d5ed64cfac4507eb18d50c39166159941a772ead725740448d6bebd |
| Prisma migrate | nguyenson1710/foodflow-migrate:sha-f2c02ed76fb6a79671c1c51d10d8b6aef0f55b8b |
sha256:8e97a9adca15fe418288f83f43056310ab36c8b72ed7636a53a15c2950dda12a |
| Admin | nguyenson1710/foodflow-admin:sha-f2c02ed76fb6a79671c1c51d10d8b6aef0f55b8b |
sha256:6f4757635d983ecf74a784749ca4aa4222066f68928a6c88e5deb0da9bf09744 |
| Restaurant | nguyenson1710/foodflow-restaurant:sha-f2c02ed76fb6a79671c1c51d10d8b6aef0f55b8b |
sha256:272ce1e2b56ac85078ccea008effdffad4e84f82ec1816026a9ae53559923753 |
The worker runs from the backend image with dist/workers/main.js; it is not a separately maintained release artifact. All four GHCR packages are public, linked to JasonTM17/FoodDelivery_App, and grant the repository Actions write access. Docker Hub and GHCR digests match for every SHA tag. No semver or latest tag was promoted.
Release promotion order:
- Build and push
sha-<full-commit>. - Pull that SHA in a clean environment; run compose smoke and block High/Critical image findings. Completed for
f2c02edby run29387565225. - Pass Supabase/Railway/Vercel production health and authenticated tracking smoke. Provider health and controlled GPS Broadcast/PostGIS smoke passed; the wider device/browser matrix remains open.
- Build and scan every supported architecture, then create the immutable release semver tag.
- Promote
latestonly through an explicit manual release action.
For self-hosting, pin IMAGE_TAG=v0.1.3 or sha-<full-commit> and use the base Compose file plus docker-compose.prod.yml. Never deploy an unverified latest tag.
| Path | Purpose |
|---|---|
backend/ |
API, Prisma schema/migrations, Vercel handler, worker entry |
web/ |
pnpm workspace: Admin, Restaurant, shared packages, Playwright |
mobile/ |
Flutter customer and driver entry points plus shared API client |
infra/ |
Compose overlays, preflight, security, and release tooling |
docs/ |
Architecture, API/OpenAPI, deployment, testing, security, product media |
e2e/ |
Cross-surface and AI smoke scenarios |
Generated output, local agent files, dotenv files, credentials, and private plans are ignored and must not be committed.
- Node.js 22.13 or newer
- Corepack + pnpm 11.11.0
- Docker Desktop/Engine
- Flutter SDK matching
mobile/pubspec.yaml - Railway CLI authenticated to the project that owns the API, worker, migrator, and Redis
- Provider credentials only for the integration being exercised
Copy only example files to ignored local dotenv files. Do not put real credentials in tracked files.
# Infrastructure
docker compose up -d postgres redis minio
# API
cd backend
corepack pnpm install --frozen-lockfile
corepack pnpm prisma generate
corepack pnpm prisma migrate dev
corepack pnpm db:seed
corepack pnpm start:dev
# Terminal A: Admin
cd ../web
corepack pnpm install --frozen-lockfile
corepack pnpm --filter foodflow-admin dev --port 3000
# Terminal B: Restaurant (from the repository root after install)
cd web
corepack pnpm --filter restaurant dev --port 3002
# Customer or Driver
cd ../mobile
flutter pub get --enforce-lockfile
flutter run --flavor customer -t lib/main_customer.dart
flutter run --flavor driver -t lib/main_driver.dartOr build the isolated full stack:
docker compose up -d --buildHealth endpoints:
- API:
GET http://localhost:3001/api/healthz - Admin:
GET http://localhost:3000/api/healthz - Restaurant:
GET http://localhost:3002/api/healthz
- Treat any key pasted into chat, logs, screenshots, tickets, or git history as exposed and rotate it before production.
- Never commit
.env, database URLs, service-role keys, JWT secrets, private keys, provider tokens, or mobile signing files. - The Supabase anon/publishable key is a public identifier but still requires RLS and origin controls. MapLibre/OpenFreeMap needs no browser API key or billing account.
- Keep DeepSeek, Supabase service role/JWT, SePay, SMTP, FCM, Twilio, database, and deployment credentials server-side.
Run preflights without printing values:
powershell -File infra/scripts/supabase-preflight.ps1
powershell -File infra/scripts/railway-preflight.ps1
powershell -File infra/scripts/vercel-web-preflight.ps1Current blockers are recorded in the Batch 4 release report. No deploy is authorized while either preflight fails.
# Complete local gate; production env/auth must be configured
powershell -File infra/scripts/local-release-gate.ps1 -RunE2EThe gate covers frozen installs, Prisma validation, backend typecheck/lint/Jest/build, web typecheck/ESLint/Vitest/build, OpenAPI Spectral, Compose config, Chromium + Firefox, Flutter analyze/test, and high-confidence secret checks. Additional release evidence includes axe serious/critical, visual regression, tenant isolation, realtime authorization, maps/routes, AI fail-closed/live smoke, and multi-architecture image scans.
The 2026-07-14 clean-volume Docker project foodflow-batch4-e2e applied all 38 migrations, seeded 201 users, 50 restaurants, 352 menu items, 509 orders, and 123 reviews, then indexed 402 RAG documents. A transaction check rejected a second default address for the same user. With explicit E2E URLs, that rebuilt stack passed all 68 cases in Chrome desktop (173.0 s), Firefox (172.9 s), and Pixel 5 mobile Chrome (117.3 s): 204/204 total. The same historical run had clean flutter analyze output and 369 passing Customer/Driver tests; see the testing guide for the newer mobile gate. Direct Chrome review also found the public Admin and Restaurant sign-in forms usable at desktop and Pixel 5 widths, with no console errors or horizontal overflow. None of this local evidence validates remote provider state, a deployed image, or live Firebase delivery.
- Rotate exposed credentials and keep all configured Railway/provider values in sealed provider stores.
- Recheck all 42 active source migrations plus the four retained rolled-back history rows. Exact Realtime and Job checksums were recovered from immutable migrator image
sha256:542510dde5c0105fb5e856487cbde851e1fefe2a2a218ca89cbd54f2d737a756; only20260712143000_add_production_storage_bucketstill lacks recoverable source provenance. Recover and review its original bytes, require the checksum audit to pass, then take a backup before any future migration rollout. Never bypass the fail-closed guard, infer provenance from schema end-state, or useprisma migrate resolveto conceal drift. - Preserve the verified Railway API/worker deployments, then deploy future releases from one immutable SHA and recheck health/readiness/worker polling.
- Run authenticated Supabase private-Broadcast allow/deny, token refresh, Storage, GPS snapshot/delta, reconnect, and tenant-isolation smoke through the live API.
- Re-smoke the exact Admin and Restaurant Vercel deployments against the current Railway API, then smoke configured maps/routes, chatbot, notifications, exports, payments, and one controlled-device FCM delivery.
- Preserve the verified immutable Docker Hub/GHCR manifests and scans; the current aliases are recorded in the release evidence section. Device/browser/provider certification remains a separate evidence gate.
- Admin guide — platform operations, support, reports, exports, and settings
- Restaurant guide — orders, menu, staff permissions, revenue, and settings
- Customer guide — ordering, permissions, map-based address selection, checkout, tracking, and support
- Driver guide — onboarding, Online/GPS, dispatch, earnings, and profile
- Product gallery
- Customer and Driver mobile guide
- Project overview and requirements
- System architecture
- API contract and OpenAPI
- Deployment guide
- Docker/local guide
- Testing guide
- AI chatbot guide
- Security guide
- Roadmap
- Branch disposition
- Batch 4 release report
master is the only long-lived release branch. Feature, fix, and dependency branches must be short-lived, pass the repository gates, merge through a pull request, and be deleted after integration. Compatible dependency updates are grouped; major runtime or tooling upgrades require an explicit migration branch. See the evidence-backed branch disposition.






