ASU SER517 Capstone Project · Group 7 · Fall 2025
A full-stack financial analytics and management platform for food truck operators — tracking purchases, sales, inventory, and menu items, with real-time reporting and secure file storage.
Note: This repository contains documentation and architecture only. The source code is maintained in a private organizational repository.
FoodTrack gives food truck operators a single platform to manage their business finances. Operators can log purchases and sales, track ingredient inventory with automatic FIFO depletion, manage menu item compositions, upload receipts to cloud storage, and generate P&L and financial reports — all behind a secure JWT-authenticated API.
The frontend UI was inherited from a previous team. Our team (Group 7) designed and implemented the entire backend from scratch:
- Designed the full relational database schema (10 tables, 7 migrations through November 2025)
- Implemented the 3-layer Controller → Service → Prisma architecture
- Built the complete purchases module: CRUD, batch CSV upload, inventory sync
- Built the complete sales module: CRUD, batch upload, FIFO inventory deduction on sync
- Built the inventory module: CRUD, batch tracking, FIFO depletion, low-stock alerts
- Built the menu module: CRUD with ingredient composition via the
Usesjunction table - Implemented JWT authentication: signup/login, 60-min access tokens, 7-day refresh tokens, OTP forgot-password via email
- Integrated AWS S3 for file upload, retrieval, rename, and delete
- Built all reports endpoints: Financial Report, P&L, Inventory Report, Sales Analytics
- Created the deployment plan (PM2, environment configuration, infrastructure documentation)
| Layer | Technologies |
|---|---|
| Frontend | Next.js 15 + TypeScript, Tailwind CSS, Redux Toolkit, Recharts, Framer Motion |
| Backend | Node.js + Express 4 + TypeScript |
| ORM | Prisma 6 |
| Database | PostgreSQL (10 tables, 7 migrations) |
| Cloud Storage | AWS S3 (foodtruck-userfiles, ca-central-1) |
| Nodemailer via Gmail SMTP | |
| Process Management | PM2 |
| Auth | JWT (access + refresh tokens), bcrypt |
The schema is user-scoped throughout — every table uses Email as part of its composite primary key, so all data is naturally partitioned per user with no risk of cross-user data leakage.
┌─────────────────────────────────────────────────────────────────────────────────┐
│ USER (PK: Email) │
│ Email · Password · BusinessName · Province · LastSaleSync · LastPurchaseSync │
└──────────────┬──────────────────────────────────────────────────────────────────┘
│ (cascade delete on all child tables)
┌───────┼────────────────────────────────────────┐
│ │ │ │ │
▼ ▼ ▼ ▼ ▼
┌──────────┐ ┌────────────┐ ┌──────────┐ ┌──────┐ ┌──────────┐
│PURCHASE │ │ INGREDIENT │ │ MENUITEM │ │ SALE │ │OTHERCOST │
│PK: Email │ │PK: Email │ │PK: Email │ │PK: │ │PK: Email │
│+DateTime │ │ +Name │ │ +Name │ │Email │ │+CostDate │
│+Location │ │Amount │ │Cost │ │+Start│ │+CostName │
│Cost │ │AmountUnits │ │ │ │+End │ │CostCat. │
└────┬─────┘ │EstimatedAmt│ └────┬─────┘ │Rev. │ │Cost │
│ └──────┬─────┘ │ │Hours │ └──────────┘
│ │ │ │Loc. │
▼ ▼ │ │Wthr. │
┌──────────┐ ┌──────────────┐ │ └──┬───┘
│INCLUDES │ │INGREDIENTBATCH│ │ │
│(line item)│ │PK: Email │ │ ▼
│PK: Email │ │+IngName │ │ ┌──────┐
│+DateTime │ │+BatchId │ │ │ SOLD │
│+Location │ │PurchaseDate │ │ │PK: │
│+IngName │ │PurchaseLoc. │ │ │Email │
│Price │ │PurchasedAmt │ │ │+Start│
│Amount │ │PricePerUnit │ │ │+End │
│Units │ │RemainingAmt │ │ │+Menu │
└──────────┘ │AmountUnits │ │ │Count │
│EstimatedAmt │ │ └──────┘
└──────────────┘ │
▼
┌──────────┐
│ USES │
│PK: Email │
│+Menu │
│+Ingred. │
│Amount │
│Price │
│Units │
└──────────┘
┌──────────────────────┐
│ CODE (OTP) │
│ PK: Email (1-to-1) │
│ Code · ExpireAt │
│ Used (bool) │
└──────────────────────┘
- Email as primary identifier throughout — no surrogate user ID. Every composite PK includes Email, so all rows are naturally user-scoped.
- FIFO inventory via IngredientBatch — each purchase creates a batch with
RemainingAmount. Sales sync depletes the oldest batch first until the quantity is satisfied. - Incremental sync with
LastSaleSync—Sold.CreatedAttimestamps drive sync; only items sold afterLastSaleSyncget deducted from inventory, preventing double-depletion on repeated syncs. - OTP is 1-to-1 with User via upsert — each forgot-password request overwrites the previous code, eliminating stale token accumulation.
- Cascade deletes — deleting a user removes all their data atomically across all 10 tables.
┌─────────────────────────────────────────────────────────────────────────────┐
│ CLIENT BROWSER │
│ Next.js 15 + Redux + RTK Query │
│ │
│ ┌──────────┐ ┌──────────┐ ┌───────────┐ ┌────────┐ ┌──────────────┐ │
│ │ Landing │ │ Login / │ │ Dashboard │ │ Redux │ │ tokenAuth.tsx│ │
│ │ Page │ │ Signup │ │ Pages │ │ Store │ │ (auto-refresh│ │
│ └──────────┘ └────┬─────┘ └─────┬─────┘ └───┬────┘ │ @ 55 min) │ │
└─────────────────────┼──────────────┼─────────────┼────────┴─────────────────┘
│ HTTP + Bearer Token (JWT)
▼
┌─────────────────────────────────────────────────────────────────────────────┐
│ EXPRESS SERVER (port 3001) │
│ │
│ ┌──────────────────────────────────────────────┐ │
│ │ MIDDLEWARE LAYER │ │
│ │ Helmet · Morgan · CORS · body-parser │ │
│ │ authenticateToken (JWT verify on all │ │
│ │ routes except /auth/*) │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────────────┐ │
│ │ CONTROLLER LAYER │ │
│ │ /auth /purchases /sales /inventory │ │
│ │ /menu /dashboard /files /delete │ │
│ └──────────────────┬───────────────────────────┘ │
│ │ │
│ ┌──────────────────▼───────────────────────────┐ │
│ │ SERVICE LAYER │ │
│ │ authService · purchaseService · salesSvc │ │
│ │ inventoryService · menuService │ │
│ │ reportsService · fileService · emailSvc │ │
│ └──────┬───────────┬─────────────┬─────────────┘ │
└─────────┼───────────┼─────────────┼─────────────────────────────────────────┘
│ │ │
▼ ▼ ▼
┌──────────────┐ ┌────────────┐ ┌──────────────────┐
│ PostgreSQL │ │ AWS S3 │ │ Gmail SMTP │
│ (Prisma 6) │ │ ca-central │ │ Nodemailer │
│ 10 tables │ │ -1 │ │ Welcome email │
│ 7 migrations│ │ │ │ OTP reset │
└──────────────┘ └────────────┘ └──────────────────┘
CLIENT SERVER DB
│ │ │
│──── POST /auth/login ────────▶│ │
│ { email, password } │──── bcrypt.compare ──────▶│
│ │◀─── User row ─────────────│
│◀─── { accessToken (60min), │ │
│ refreshToken (7days) }──│ │
│ │ │
│──── GET /any-route ──────────▶│ │
│ Authorization: Bearer <AT>│── jwt.verify(AT) ─────────│
│◀─── 200 + data ───────────────│ req.user = { email } │
│ │ │
│ [tokenAuth.tsx fires @ 55min]│ │
│──── POST /auth/refresh ──────▶│ │
│ { refreshToken } │── jwt.verify(RT) │
│◀─── { new accessToken } ───────│ issue new AT │
| Module | Status | Notes |
|---|---|---|
| Auth (signup/login/refresh/OTP) | ✅ Complete | JWT access + refresh, bcrypt, email OTP |
| Purchases | ✅ Complete | CRUD + batch CSV upload + inventory sync |
| Sales | ✅ Complete | CRUD + batch upload + FIFO inventory deduction |
| Inventory | ✅ Complete | CRUD + batch tracking + FIFO depletion + low-stock alerts |
| Menu | ✅ Complete | CRUD + ingredient composition via Uses table |
| File Storage | ✅ Complete | Upload/retrieve/rename/delete via AWS S3 |
| Reports (backend) | ✅ Complete | P&L, financial report, inventory report, sales analytics |
| Dashboard metrics (frontend wiring) | 🔶 Partial | Endpoints exist; frontend still uses hardcoded mock data |
| Budget page | ❌ Stub | Renders placeholder only |
| Expenses page | ❌ Stub | Renders placeholder only |
| Deployment | 📋 Planned | PM2 config complete; Dockerfile and CI/CD not implemented |
These were identified and documented as pre-deployment requirements:
- Gmail SMTP app password is hardcoded in
emailService.ts— must move to environment variable - Default JWT secret is
"your-secret-key"in source — dangerous without env override - PM2 config uses
npm run devinstead ofnpm start - Hardcoded dev email in the inventory page
Full setup instructions are in the private organizational repository. High-level steps:
# 1. Start PostgreSQL (Docker)
docker run --name ftaac-postgres \
-e POSTGRES_PASSWORD=postgres \
-e POSTGRES_DB=ftaac_dev \
-p 5432:5432 -d postgres:15
# 2. Server setup
cd codebase/server
npm install
# Add .env with DATABASE_URL, JWT_SECRET, AWS credentials, SMTP config
npx prisma migrate dev
npm run seed
npm run dev
# 3. Client setup
cd codebase/client
npm install --legacy-peer-deps
npm run devThis project was built for the Food Truck Association of Canada as part of the ASU SER517 capstone program.
Source code available upon request.