397 lines
15 KiB
Markdown
397 lines
15 KiB
Markdown
# API Architecture and Route Design
|
|
|
|
This document explains how the API is structured today, how requests move through the stack, and what each route group is responsible for.
|
|
|
|
Source of truth for the runtime wiring:
|
|
|
|
- `apps/api/src/app.ts`
|
|
- `apps/api/src/modules/*`
|
|
- `apps/api/src/middleware/*`
|
|
- `apps/api/src/swagger/openapi.ts`
|
|
|
|
## Runtime Overview
|
|
|
|
The API is an Express application mounted under `/api/v1` with a few non-versioned utility endpoints:
|
|
|
|
- `GET /health` returns process health.
|
|
- `GET /docs` serves Swagger UI.
|
|
- `GET /api/v1/openapi.json` returns the generated OpenAPI document.
|
|
- `/storage/*` serves uploaded assets such as logos, hero images, vehicle photos, and reservation/customer documents.
|
|
|
|
The app boot sequence in `apps/api/src/app.ts` is intentionally ordered:
|
|
|
|
1. CORS is applied first.
|
|
2. storage guards are applied before static serving so private customer license files are never anonymously retrievable.
|
|
3. Swagger UI is mounted before Helmet so the UI assets are not blocked by CSP.
|
|
4. webhook routes that require raw payload handling are mounted before `express.json()`.
|
|
5. Helmet, request logging, JSON parsing, and the module routers are mounted.
|
|
6. the centralized error middleware converts validation, Prisma, and application errors into consistent JSON responses.
|
|
|
|
## Request Lifecycle
|
|
|
|
Most internal company routes follow the same pattern:
|
|
|
|
1. Express router receives the request.
|
|
2. Zod schemas validate `req.params`, `req.query`, and `req.body` through `parseParams`, `parseQuery`, and `parseBody`.
|
|
3. auth middleware resolves the caller identity.
|
|
4. tenant middleware resolves the company record.
|
|
5. subscription middleware blocks suspended or pending companies where required.
|
|
6. role middleware enforces employee or admin permissions.
|
|
7. the route handler calls a service function.
|
|
8. the service applies business rules and calls a repo or Prisma directly.
|
|
9. a presenter may normalize the payload for frontend consumption.
|
|
10. `ok()` or `created()` wraps successful responses in `{ "data": ... }`.
|
|
|
|
Common exceptions:
|
|
|
|
- `GET /health` and `GET /api/v1/docs` return raw JSON, not the `{ data }` envelope.
|
|
- webhook endpoints return minimal receipt payloads.
|
|
- some public endpoints return graceful fallback responses if the database is unavailable.
|
|
|
|
## Security and Access Model
|
|
|
|
There are four main access patterns.
|
|
|
|
### Employee JWT
|
|
|
|
`requireCompanyAuth` validates a Bearer token with `type === "employee"`, loads the employee, and attaches:
|
|
|
|
- `req.employee`
|
|
- `req.company`
|
|
- `req.companyId`
|
|
|
|
This is the default for dashboard/company management routes.
|
|
|
|
### Tenant Resolution
|
|
|
|
`requireTenant` reloads the company from `req.companyId` and guarantees the tenant context exists. Company-scoped modules then query by `companyId`.
|
|
|
|
### Subscription Guard
|
|
|
|
`requireSubscription` blocks company routes when the company status is `PENDING` or `SUSPENDED`. Public site and marketplace routes are intentionally not behind this guard.
|
|
|
|
### Role-Based Access Control
|
|
|
|
Employee roles are hierarchical:
|
|
|
|
- `OWNER`
|
|
- `MANAGER`
|
|
- `AGENT`
|
|
|
|
Admin roles are hierarchical:
|
|
|
|
- `SUPER_ADMIN`
|
|
- `ADMIN`
|
|
- `SUPPORT`
|
|
- `FINANCE`
|
|
- `VIEWER`
|
|
|
|
### Renter JWT
|
|
|
|
`requireRenterAuth` validates a Bearer token with `type === "renter"` and attaches `req.renterId`.
|
|
|
|
`optionalRenterAuth` is used on marketplace routes so public reads still work without a token.
|
|
|
|
### API Key
|
|
|
|
`requireApiKey` accepts a company public API key in `x-api-key`. This is the low-friction integration path for public company-site style calls.
|
|
|
|
## Implementation Pattern
|
|
|
|
The API codebase is organized per module. The most common pattern is:
|
|
|
|
- `*.routes.ts`: route definitions and middleware composition
|
|
- `*.schemas.ts`: Zod input validation
|
|
- `*.service.ts`: business rules and orchestration
|
|
- `*.repo.ts`: Prisma access helpers
|
|
- `*.presenter.ts`: response shaping
|
|
|
|
The `vehicles` module is representative:
|
|
|
|
- `vehicle.routes.ts` mounts auth, tenant, and subscription guards once for the router.
|
|
- route handlers validate input and call service functions.
|
|
- `vehicle.service.ts` contains business rules such as publish-state normalization and availability calculation.
|
|
- `vehicle.repo.ts` owns the Prisma queries and enforces tenant filters in reads.
|
|
- `vehicle.presenter.ts` shapes the response returned to clients.
|
|
|
|
Some modules split specialized workflows into additional services instead of one large file. The reservations module is the clearest example:
|
|
|
|
- `reservation.service.ts` handles CRUD and list behavior
|
|
- `reservation.lifecycle.service.ts` handles confirm/checkin/checkout/close/extend/cancel transitions
|
|
- `reservation.inspection.service.ts` handles pickup/dropoff inspections
|
|
- `reservation.additional-driver.service.ts` handles approval workflows
|
|
- `reservation.photo.service.ts` handles pickup/dropoff photo uploads
|
|
- `reservation.document.service.ts` generates contract and billing payloads
|
|
|
|
## Route Groups
|
|
|
|
The table below describes the current route groups mounted in `app.ts`.
|
|
|
|
| Base path | Access | Purpose | Notes |
|
|
| --- | --- | --- | --- |
|
|
| `/health` | Public | Process health | Non-versioned utility endpoint |
|
|
| `/docs` | Public | Swagger UI | Backed by `openapi.ts` |
|
|
| `/api/v1/openapi.json` | Public | OpenAPI JSON | Generated from Zod schemas and manual path entries |
|
|
| `/api/v1/auth/company` | Public | Company signup | `complete-signup` and `verify-email` now return disabled errors |
|
|
| `/api/v1/auth/employee` | Public plus employee JWT | Employee login, password reset, profile, language | Dashboard/staff authentication path |
|
|
| `/api/v1/auth/renter` | Renter JWT for profile routes | Renter profile and push token | `signup` and `login` are currently disabled in this codebase |
|
|
| `/api/v1/admin` | Admin JWT | Platform operations | Includes company admin, billing, subscriptions, pricing, audit, and admin-user management |
|
|
| `/api/v1/marketplace` | Public, optional renter JWT | Marketplace discovery and reservation intake | Designed for discovery and lead capture across companies |
|
|
| `/api/v1/site` | Public | White-label company site APIs | Drives each company-branded booking site |
|
|
| `/api/v1/subscriptions` | Mixed | Plan listing, webhooks, subscription lifecycle | Public, webhook, and authenticated sub-routers share the same prefix |
|
|
| `/api/v1/vehicles` | Employee JWT + tenant + subscription | Fleet management | Includes photos, status, calendar blocks, maintenance, and availability |
|
|
| `/api/v1/reservations` | Employee JWT + tenant + subscription | Reservation operations | Central booking lifecycle for internal staff |
|
|
| `/api/v1/team` | Employee JWT + tenant + subscription | Employee/team management | Owner-controlled invite, role, activation, removal |
|
|
| `/api/v1/customers` | Employee JWT + tenant + subscription | Company CRM and license handling | Includes protected license-image retrieval and approval flows |
|
|
| `/api/v1/offers` | Employee JWT + tenant + subscription | Promotional offers | Company marketing and promo codes |
|
|
| `/api/v1/analytics` | Employee JWT + tenant + subscription | Dashboard metrics and reports | Includes summary, dashboard, source, and report routes |
|
|
| `/api/v1/notifications` | Employee or renter JWT depending on route | Notification inbox and preferences | Separate company and renter surfaces |
|
|
| `/api/v1/companies` | Employee JWT + tenant + subscription | Company profile and settings | Brand, domains, contract settings, insurance policies, pricing rules, accounting, API key |
|
|
| `/api/v1/payments` | Mixed | Rental payment webhooks and company payment actions | Webhooks are public; operational routes are company-authenticated |
|
|
| `/api/v1/reviews` | Employee JWT + tenant + subscription | Review moderation | Company-side review visibility and replies |
|
|
| `/api/v1/complaints` | Employee JWT + tenant + subscription | Complaint handling | Internal complaint tracking |
|
|
| `/api/v1/webhooks` | Public | External webhook receivers | Currently includes `/clerk` placeholder |
|
|
|
|
## Functional Breakdown by Module
|
|
|
|
### Company and employee onboarding
|
|
|
|
- `POST /api/v1/auth/company/signup` creates the company tenant, owner employee, default subscription state, and related baseline records in one transaction.
|
|
- The signup flow generates a unique company slug, hashes the owner password, starts a 90-day trial window, and sends account-created notifications.
|
|
- Employee auth supports login, forgot-password, reset-password, profile read, and language updates.
|
|
|
|
### Company profile and business configuration
|
|
|
|
The `companies` module owns tenant-level settings:
|
|
|
|
- company identity
|
|
- brand settings and asset upload
|
|
- subdomain/custom-domain configuration
|
|
- contract settings used by rental documents
|
|
- insurance policies
|
|
- pricing rules
|
|
- accounting settings
|
|
- public API key generation/regeneration
|
|
|
|
This module is where most long-lived company configuration lives.
|
|
|
|
### Fleet management
|
|
|
|
The `vehicles` module handles:
|
|
|
|
- CRUD for vehicles
|
|
- publish/unpublish
|
|
- explicit status updates
|
|
- photo upload and deletion
|
|
- availability checks
|
|
- monthly vehicle calendar events
|
|
- manual calendar blocks
|
|
- maintenance logs
|
|
|
|
Uploads are persisted through the storage service, then surfaced under `/storage/*`.
|
|
|
|
### Reservations and rental workflow
|
|
|
|
The reservation aggregate is the center of the operational API.
|
|
|
|
Important transitions:
|
|
|
|
1. create draft reservation
|
|
2. confirm reservation after license checks
|
|
3. check in and move the reservation to `ACTIVE`
|
|
4. check out and mark the reservation `COMPLETED`
|
|
5. close the reservation operationally after post-rental tasks are done
|
|
|
|
The lifecycle service also handles:
|
|
|
|
- extensions with conflict checks
|
|
- cancellations
|
|
- vehicle status synchronization
|
|
- review token generation
|
|
- review request email dispatch on close
|
|
|
|
The reservations module also owns:
|
|
|
|
- contract and billing payload generation
|
|
- pickup/dropoff inspections
|
|
- additional-driver approval
|
|
- pickup/dropoff photo upload
|
|
|
|
### Customer CRM and license compliance
|
|
|
|
Customers are company-scoped CRM records, even when a renter identity also exists. The `customers` module supports:
|
|
|
|
- customer CRUD
|
|
- flag/unflag flows
|
|
- license image upload and protected retrieval
|
|
- license validation and manager approval
|
|
|
|
This is intentionally separate from renter identity because one renter may interact with multiple companies while each company still needs its own operational customer record.
|
|
|
|
### Marketplace and public site
|
|
|
|
The public surface is split in two modules on purpose.
|
|
|
|
`marketplace` is the cross-company discovery layer:
|
|
|
|
- featured/public offers
|
|
- marketplace cities
|
|
- listed companies
|
|
- vehicle search
|
|
- marketplace reservation intake
|
|
- review submission by token
|
|
- company public pages under `/:slug`
|
|
|
|
`site` is the white-label company booking API:
|
|
|
|
- company brand payload
|
|
- published vehicle catalog
|
|
- booking options
|
|
- date-range availability
|
|
- promo validation
|
|
- booking creation
|
|
- payment initialization
|
|
- booking lookup
|
|
- company contact form
|
|
|
|
Both modules deliberately include graceful degradation paths when the database is unavailable so public pages can fail softer than internal operations.
|
|
|
|
### Payments
|
|
|
|
Rental payments are company-side operational payments, distinct from SaaS subscription billing.
|
|
|
|
The `payments` module handles:
|
|
|
|
- AmanPay and PayPal rental webhooks
|
|
- listing payments by company or reservation
|
|
- initializing online charges
|
|
- capturing PayPal orders
|
|
- recording manual payments
|
|
- refunding successful online payments
|
|
|
|
Payment success updates both the payment record and the reservation paid amount.
|
|
|
|
### Subscriptions and SaaS billing
|
|
|
|
The `subscriptions` module owns the platform billing lifecycle:
|
|
|
|
- public plan/provider/feature listing
|
|
- company subscription read endpoints
|
|
- trial start
|
|
- checkout
|
|
- plan changes
|
|
- cancellation/resume/reactivation
|
|
- provider webhooks
|
|
- admin overrides
|
|
|
|
It also exposes background-job style functions for:
|
|
|
|
- trial expiration
|
|
- payment-pending timeout
|
|
- past-due timeout
|
|
- suspension timeout
|
|
- period-end cancellation
|
|
|
|
### Notifications
|
|
|
|
Notifications are multi-channel and multi-audience:
|
|
|
|
- company inbox
|
|
- renter inbox
|
|
- unread counts
|
|
- preferences per type and channel
|
|
- notification history
|
|
|
|
Templates and preference records live in the database; delivery orchestration lives in `services/notificationService.ts`.
|
|
|
|
### Admin surface
|
|
|
|
The admin router is broad because it covers platform operations across multiple domains:
|
|
|
|
- admin authentication and 2FA
|
|
- company listing, inspection, status changes, deletion, impersonation
|
|
- renter blocking/unblocking
|
|
- platform metrics
|
|
- notification inspection
|
|
- audit log access
|
|
- admin-user and permission management
|
|
- billing account and billing invoice operations
|
|
- pricing config, plan features, and promotions
|
|
- subscription overrides and extensions
|
|
- marketplace homepage configuration
|
|
|
|
This is the only route group allowed to work across tenants.
|
|
|
|
## Important Workflow Examples
|
|
|
|
### 1. Company signup
|
|
|
|
`POST /api/v1/auth/company/signup`
|
|
|
|
- validates the payload with Zod
|
|
- checks for duplicate company and owner email addresses
|
|
- generates a unique slug
|
|
- hashes the owner password
|
|
- creates the tenant, owner employee, and initial subscription state in one transaction
|
|
- sends localized account-created notifications
|
|
|
|
### 2. Public booking from a company site
|
|
|
|
`POST /api/v1/site/:slug/book`
|
|
|
|
- resolves the company from the slug
|
|
- verifies vehicle availability for the requested date range
|
|
- upserts the company-scoped customer record
|
|
- computes total days and base rental price
|
|
- applies promo code discount if present
|
|
- applies pricing rules
|
|
- stores a draft reservation
|
|
- attaches reservation insurance snapshots and additional-driver snapshots
|
|
- triggers license validation flags
|
|
|
|
Payment is then started with `POST /api/v1/site/:slug/booking/:id/pay`.
|
|
|
|
### 3. Reservation lifecycle after booking
|
|
|
|
- reservation starts in `DRAFT`
|
|
- staff confirms it after license compliance checks
|
|
- vehicle status moves to `RESERVED`
|
|
- checkin moves reservation to `ACTIVE` and vehicle to `RENTED`
|
|
- checkout moves reservation to `COMPLETED`, records mileage, and generates a review token
|
|
- close marks the operational workflow complete and can send the review request email
|
|
|
|
## Error Handling and Response Shape
|
|
|
|
The API standardizes error handling in `http/errors/errorMiddleware.ts`.
|
|
|
|
Special handling exists for:
|
|
|
|
- Zod validation errors -> `400 validation_error`
|
|
- Prisma `P2025` -> `404 not_found`
|
|
- Prisma `P2002` -> `409 conflict`
|
|
- custom `AppError` subclasses -> explicit status and machine-readable code
|
|
|
|
Successful route handlers usually return:
|
|
|
|
```json
|
|
{
|
|
"data": {}
|
|
}
|
|
```
|
|
|
|
Common deviations:
|
|
|
|
- `/health`
|
|
- `/api/v1/docs`
|
|
- webhook receipt payloads
|
|
- file downloads such as admin invoice PDFs
|
|
|
|
## Documentation Artifacts
|
|
|
|
There are two documentation layers in the codebase:
|
|
|
|
1. runtime API docs from Swagger/OpenAPI at `/docs` and `/api/v1/openapi.json`
|
|
2. this markdown document, which explains design decisions, route grouping, and request flow
|
|
|
|
The OpenAPI file is useful for request/response contracts, but it is not yet a perfect mirror of every newer route. This document should be updated together with `app.ts`, route files, and the OpenAPI generator whenever route groups change.
|