# Gate Pass — Codebase Reference

> Visitor management SaaS. Two independent apps (`api/` + `web/`) in one repo; no monorepo tooling (no Nx/Turborepo). The web app compiles to three targets: desktop browser, iOS/Android (Capacitor), and Electron — all from the same React codebase.

---

## Quick Navigation

| Document | What's inside |
|----------|--------------|
| [CODEBASE_WEB.md](./CODEBASE_WEB.md) | React frontend — pages, components, routes, hooks, socket, Web Push, offline |
| [CODEBASE_API.md](./CODEBASE_API.md) | Express backend — all API routes, DB models, auth, push (FCM + VAPID), cron jobs |
| [CODEBASE_IOS.md](./CODEBASE_IOS.md) | iOS Capacitor app — native plugins, FCM bridge, push flow, offline cache, build |

---

## Repository Structure

```
apps/
├── api/                        # Express.js backend (TypeScript)
│   └── src/
│       ├── controllers/        # Business logic
│       ├── routes/             # API endpoint definitions
│       ├── middleware/         # Auth, error handling
│       ├── prisma/             # Schema (schema.prisma)
│       ├── lib/                # Events, FCM push, web push, cron, email
│       ├── config/             # DB + mailer + storage setup
│       ├── scripts/            # seed, smoke, test-push, etc.
│       └── server.ts           # Express + Socket.IO entry point
│
└── web/                        # React + Vite frontend
    └── src/
        ├── pages/              # Route-level components
        │   ├── auth/           # Login + OTP (desktop)
        │   ├── dashboard/      # Owner dashboard
        │   ├── visitors/       # Visitor CRUD + scanner
        │   ├── admins/         # Sub-admin management
        │   ├── approvers/      # Approver management
        │   ├── approvals-mobile/  # Mobile-only screens
        │   ├── walk-in*/       # Walk-in QR + public form
        │   ├── notifications/  # Notification inbox
        │   ├── settings/       # Workspace settings
        │   └── platform/       # Vendor console
        ├── components/
        │   ├── layout/         # AppShell (desktop) + AppShellMobile
        │   ├── shared/         # Notifications bell, scanner, user menu
        │   └── ui/             # Buttons, inputs, modals, badges
        ├── hooks/              # useSocket, usePushNotifications, etc.
        ├── store/              # Zustand stores (auth, notifications, counts)
        ├── lib/                # Axios instances, capacitor helpers, offline DB
        ├── routes.tsx          # Desktop router
        ├── routes.mobile.tsx   # Mobile (Capacitor) router
        ├── App.tsx             # Desktop entry point
        └── AppMobile.tsx       # Mobile (Capacitor) entry point
    └── ios/App/App/
        ├── AppDelegate.swift   # Firebase init + FCM token bridge
        ├── Info.plist          # ATS config + push entitlements
        └── GoogleService-Info.plist  # Firebase project config
```

---

## Tech Stack Summary

### Frontend (Web + Mobile + Electron)
React 19 · Vite 8 · React Router 6 · Zustand · TanStack Query · Axios · Tailwind CSS 4 · Socket.IO client · Capacitor 8

### Backend API
Node.js + TypeScript · Express 4 · Socket.IO 4 · Prisma 5 · PostgreSQL · Firebase Admin SDK 13 · web-push · Nodemailer · JWT + bcrypt

### iOS / Android
Capacitor 8 wrapping the same React web app in a WKWebView (not React Native). CocoaPods for native dependencies. Firebase/Messaging via CocoaPods for FCM.

---

## User Roles

| Role | Model | Login method | Primary UI |
|------|-------|-------------|------------|
| Owner | `Owner` | Email OTP | Web desktop |
| Admin / Sub-Admin | `Admin` | Email OTP | Web desktop |
| Approver (legacy) | `Approver` | Email OTP | iOS mobile app |
| Checkpoint / Reception | `VisitorCheckpoint` | Username + password (bcrypt) | Web scanner page |
| Platform Admin (vendor) | `PlatformAdmin` | Email OTP | Web `/platform/*` |
| Walk-in Visitor | — (anonymous) | None — public QR form | `/walk-in/:code` |

---

## Visitor Status State Machine

```
EXPECTED
  ├─> AWAITING_APPROVAL  (requiresApproval=true, after QR scan or walk-in form)
  │     ├─> ARRIVED       (approved by owner/approver → check-in completes)
  │     └─> REJECTED      (denied by owner/approver)
  ├─> ARRIVED            (auto check-in: requiresApproval=false, or pre-approved walk-in)
  │     └─> CHECKED_OUT  (manual by owner/admin, or nightly auto-checkout cron)
  ├─> CANCELLED          (auto-cancel cron or manual owner action)
  └─> EXPIRED            (past visit date, never acted on)
```

Frequent visitors (`isFrequent=true`) reset back to `EXPECTED` on each re-scan so the same QR works indefinitely.

---

## Push Notification Architecture

```
iOS App (Capacitor WebView)
  └── usePushNotifications hook
        ├── PushNotifications.register()  →  APNs
        ├── AppDelegate.didRegisterForRemoteNotifications  →  Firebase SDK
        ├── MessagingDelegate.didReceiveRegistrationToken  →  UserDefaults["CapacitorStorage.fcm_token"]
        ├── Preferences.get({ key: 'fcm_token' })  →  FCM token in JS
        └── POST /approver/devices/register  or  /visitors/devices/register
              └── DB: DeviceToken table

API (Express)
  └── recordNotification({ push: { data: {...} } })
        └── sendPushToRecipient()
              └── firebase-admin messaging.sendEachForMulticast()
                    └── FCM → APNs → iOS device
```

---

## Environments

| Env | Web URL | API URL |
|-----|---------|---------|
| Production | `https://web.gp.vcarrd.worksqr.com` | `https://api.gp.vcarrd.worksqr.com/api` |
| Local dev | `http://localhost:3200` | `http://localhost:4200/api` |
| Mobile build | Capacitor bundle | `https://api.gp.vcarrd.worksqr.com/api` (`.env.mobile`) |

---

## Running Locally

```bash
# API (port 4200)
cd apps/api && npm run dev

# Web desktop (port 3200)
cd apps/web && npm run dev

# Mobile build (Capacitor sync to Xcode)
cd apps/web && npm run build:mobile && npx cap sync ios

# Test push notifications end-to-end
cd apps/api && npm run test:push

# Seed database with dummy data
cd apps/api && npm run seed

# DB schema push (no migration history)
cd apps/api && npm run prisma:push
```

---

## Key Files

| File | Purpose |
|------|---------|
| `api/src/server.ts` | Express app setup, CORS, Socket.IO, route mounting |
| `api/src/prisma/schema.prisma` | Full DB schema — 19+ models |
| `api/src/controllers/visitors.controller.ts` | Core visitor CRUD + approval workflows |
| `api/src/controllers/visitor-scanner.controller.ts` | Checkpoint scan + check-in + push triggers |
| `api/src/lib/events.ts` | `recordNotification()` — DB + socket + push dispatch |
| `api/src/lib/push.ts` | Firebase Admin FCM sender |
| `api/src/lib/webPush.ts` | VAPID web push sender |
| `api/src/middleware/auth.ts` | JWT verification + role extraction |
| `api/src/scripts/test-push.ts` | CLI tool to smoke-test FCM end-to-end |
| `web/src/routes.tsx` | Desktop router |
| `web/src/routes.mobile.tsx` | Mobile router (5 screens) |
| `web/src/hooks/usePushNotifications.ts` | FCM token registration + notification listeners |
| `web/src/store/auth.store.ts` | Zustand auth state (token, role, logout) |
| `web/src/lib/axios.ts` | Main Axios instance — JWT inject + 401 handler |
| `web/src/lib/offlineDb.ts` | SQLite offline cache helpers |
| `web/src/lib/outboxDrain.ts` | Offline request queue + drain on reconnect |
| `web/ios/App/App/AppDelegate.swift` | Firebase init + FCM token → UserDefaults bridge |
| `web/capacitor.config.ts` | Capacitor iOS/Android WebView config |
