# Trip Track backend (NestJS)

MongoDB-backed API and Socket.IO gateway for the Rayzon Solar / Trip Track employee travel app. Replaces Firebase Auth, Firestore, Storage snapshots, and live listeners with **REST + JWT**, **Socket.IO rooms**, and **presigned S3-style uploads**.

## Quick start

1. Copy `.env.example` to `.env` and set secrets (at least `JWT_*` and `MONGODB_URI`).
2. Start MongoDB: `docker compose up -d`
3. Seed an admin user (optional if collection is empty): `npm run seed`
4. Run API: `npm run start:dev`
5. OpenAPI UI: `http://localhost:3000/api/docs`  
   OpenAPI JSON: `http://localhost:3000/api/docs-json`

## Architecture choices

- **Route points**: Stored in a dedicated MongoDB collection `route_points` (not embedded on `travel_requests`) for high write volume, `{ requestId, timestamp }` range queries for replay/reports, and optional future TTL/archival jobs without rewriting large parent documents. Idempotency: unique index on `(requestId, pointId)` with upserts on batch ingest (same as Hive batch replay).
- **Dates in JSON**: All API bodies/responses use **ISO8601 strings** for dates (Express/Mongoose JSON serialization). Align Flutter parsing with this single convention (avoid mixing Unix ms unless you add a documented alternate).

## Modules

| Module            | Responsibility |
|-------------------|----------------|
| `auth`            | Register (pending-invite flow), login, refresh, logout, password reset hooks + noop email provider |
| `users`           | Profiles (`uid`, `email`, `name`, `employeeCode`, `role`, `status`), admin CRUD / ban |
| `pending-users`   | Admin creates pending rows; approve; user `register` consumes approved invite |
| `travel-requests` | CRUD + PATCH with deep merge for Firestore-shaped payloads (`strict: false` schema) |
| `route-points`    | Batch append (max 40), paginated list by time |
| `files`           | Presigned PUT for meter images under `images/start_meter_images/...` and `images/end_meter_images/...` |
| `audit-logs`      | Append-only admin action trail |
| `admin`           | Live trips snapshot (HTTP) |
| `reports`         | User-wise and daily aggregates + CSV/JSON export (admin) |
| `realtime`        | Socket.IO gateway + server push for REST PATCH and location batches |

## Flutter / Firestore compatibility

- **Travel requests**: Top-level fields mirror common `TravelRequestModel.toFirebase()` keys (`requestId`, `userId`, `userName`, `name`, `city`, `fromLocation`, `toLocation`, `vehicleType`, `requestDate`, `status`, odometer and image URLs, `tripLegs`, aggregates, `trackingSessionId`, `tripStartedAt`, `tripEndedAt`, `trackingStatus`, `enableLiveTracking`, etc.). Unknown keys are accepted on create/PATCH thanks to `strict: false`.
- **Route points**: Fields align with `RoutePointModel.toFirestoreMap()` (`pointId`, `requestId`, `legId`, `sessionId`, `timestamp`, `latitude`, `longitude`, optional `accuracy`, `speed`, `heading`, `altitude`, `isMoving`, `isStopMarker`, `source`).

## Socket.IO event catalog

Default namespace `/`. Authenticate with **JWT access token**:

- Handshake: `auth: { token: "<accessToken>" }` **or** header `Authorization: Bearer <accessToken>`.

### Client → server

| Event | Payload | Notes |
|-------|-----------|--------|
| `join:request` | `{ requestId: string }` | Join room `request:{requestId}` if caller may read that request |
| `leave:request` | `{ requestId: string }` | Leave room |
| `location:batch` | `{ requestId, sessionId?, points: RoutePoint[] }` | Same rules as REST batch (max 40); persists then server broadcasts |
| `admin:subscribe` | optional filter object | **Admin only**; joins `admin:live` |
| `admin:unsubscribe` | — | Leaves `admin:live` |

### Server → clients

| Event | Payload | Rooms |
|-------|-----------|--------|
| `travel_request:updated` | `{ requestId, travelRequest }` | `request:{requestId}`, `admin:live` |
| `location:update` | `{ requestId, userId, userName, legId?, sessionId?, lastPoint }` | same |

`lastPoint` contains the newest persisted sample (`pointId`, `latitude`, `longitude`, `speed`, `timestamp` ISO).

## Security notes

- HTTP: `Authorization: Bearer <accessToken>` on protected routes.
- Sockets: JWT validated on connect; `location:batch` enforces request ownership and **active** `trackingSessionId` when `enableLiveTracking` and `trackingStatus` are `tracking` or `paused`.
- Global rate limits: `ThrottlerGuard` + tighter limits on `auth` and route batch endpoints.

## Scripts

- `npm run seed` — creates first admin from `SEED_ADMIN_EMAIL` / `SEED_ADMIN_PASSWORD` (see `.env.example`) when `users` is empty.
- `npm run test:e2e` — spins in-memory MongoDB, exercises auth, travel request, batch points, admin report.

## Production checklist

- Replace `NoopEmailProvider` with a real `EmailProvider` binding for password reset mail.
- Configure `S3_*` (or compatible) for real presigned meter uploads.
- Tighten `app.enableCors` to explicit mobile/web origins instead of `origin: true`.
