758 lines
26 KiB
Markdown
758 lines
26 KiB
Markdown
# Backend Routes Module Documentation
|
|
|
|
## Overview
|
|
|
|
The Routes module defines all HTTP endpoints for the application. It uses a factory pattern for entity CRUD operations and custom routes for specialized functionality.
|
|
|
|
**Directory:** `src/routes/`
|
|
|
|
**Files (25 total):**
|
|
| File | Lines | Pattern | Description |
|
|
|------|-------|---------|-------------|
|
|
| `auth.ts` | 327 | Custom | Authentication endpoints |
|
|
| `file.ts` | 150 | Custom | File upload/download endpoints |
|
|
| `publish.ts` | 107 | Custom | Publishing workflow endpoints |
|
|
| `search.ts` | 64 | Custom | Global search endpoint |
|
|
| `runtime-context.ts` | 16 | Custom | Runtime context inspection |
|
|
| `projects.ts` | 46 | Hybrid | Projects CRUD + custom clone endpoint |
|
|
| `users.ts` | 64 | Hybrid | Users CRUD via factory + sanitized GET by ID |
|
|
| `tour_pages.ts` | 380 | Manual CRUD | Tour pages CRUD plus reorder, duplicate, reverse-video status |
|
|
| `roles.ts` | 141 | Factory | Roles CRUD via factory |
|
|
| `permissions.ts` | 188 | Factory | Permissions CRUD via factory |
|
|
| `assets.ts` | 155 | Factory | Assets CRUD via factory |
|
|
| `asset_variants.ts` | 147 | Factory | Asset variants CRUD via factory |
|
|
| `access_logs.ts` | 145 | Factory | Access logs CRUD via factory |
|
|
| `project_memberships.ts` | 145 | Factory | Project memberships CRUD via factory |
|
|
| `project_audio_tracks.ts` | 151 | Factory | Project audio tracks CRUD via factory |
|
|
| `global_transition_defaults.ts` | 145 | Custom | Runtime-readable global transition defaults |
|
|
| `global_ui_control_defaults.ts` | 79 | Custom | Runtime-readable global UI-control defaults |
|
|
| `project_transition_settings.ts` | 212 | Custom | Project/environment transition overrides |
|
|
| `project_ui_control_settings.ts` | 125 | Custom | Project/environment global UI-control overrides |
|
|
| `presigned_url_requests.ts` | 150 | Factory | Presigned URL requests CRUD via factory |
|
|
| `publish_events.ts` | 157 | Factory | Publish events CRUD via factory |
|
|
| `pwa_caches.ts` | 148 | Factory | PWA caches CRUD via factory |
|
|
| `element_type_defaults.ts` | 12 | Factory | Element type defaults via factory |
|
|
| `project_element_defaults.ts` | 92 | Hybrid | Project element defaults CRUD + custom (reset, diff) |
|
|
|
|
---
|
|
|
|
## Architecture Diagram
|
|
|
|
```
|
|
┌──────────────────────────────────────────────────────────────────────┐
|
|
│ Route Patterns │
|
|
├──────────────────────────────────────────────────────────────────────┤
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Factory Pattern (createEntityRouter) │ │
|
|
│ │ │ │
|
|
│ │ routes/roles.ts ──────────────┐ │ │
|
|
│ │ routes/permissions.ts ────────┤ │ │
|
|
│ │ routes/assets.ts ─────────────┼───▶ factories/router.factory.ts│ │
|
|
│ │ routes/asset_variants.ts ─────┤ │ │
|
|
│ │ routes/access_logs.ts ────────┤ Generates: │ │
|
|
│ │ routes/pwa_caches.ts ─────────┤ • POST / │ │
|
|
│ │ routes/publish_events.ts ─────┤ • POST /bulk-import │ │
|
|
│ │ routes/element_type_defaults.ts • PUT /:id │ │
|
|
│ │ routes/presigned_url_requests.ts • DELETE /:id │ │
|
|
│ │ routes/project_memberships.ts • POST /deleteByIds │ │
|
|
│ │ routes/project_audio_tracks.ts • GET / │ │
|
|
│ │ ... (11 entities) • GET /count │ │
|
|
│ │ • GET /autocomplete │ │
|
|
│ │ • GET /:id │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Hybrid Pattern (Factory + Custom Routes) │ │
|
|
│ │ │ │
|
|
│ │ routes/projects.ts ──────────────────┐ │ │
|
|
│ │ • Standard CRUD (manual) │ │ │
|
|
│ │ + POST /:id/clone │ │ │
|
|
│ │ + GET /:id/offline-manifest │ │ │
|
|
│ │ │ │ │
|
|
│ │ routes/project_element_defaults.ts ──┘ │ │
|
|
│ │ • Standard CRUD (factory) │ │
|
|
│ │ + POST /:id/reset │ │
|
|
│ │ + GET /:id/diff │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
│ │
|
|
│ ┌─────────────────────────────────────────────────────────────────┐ │
|
|
│ │ Custom Pattern (Full Manual Implementation) │ │
|
|
│ │ │ │
|
|
│ │ routes/auth.ts ───────── Authentication flows │ │
|
|
│ │ routes/file.ts ───────── File upload/download │ │
|
|
│ │ routes/publish.ts ────── Publishing workflow │ │
|
|
│ │ routes/search.ts ─────── Global search │ │
|
|
│ │ routes/runtime-context.ts ─ Runtime inspection │ │
|
|
│ └─────────────────────────────────────────────────────────────────┘ │
|
|
└──────────────────────────────────────────────────────────────────────┘
|
|
```
|
|
|
|
---
|
|
|
|
## Route Factory
|
|
|
|
### factories/router.factory.ts
|
|
|
|
Generates standardized CRUD routes for entities.
|
|
|
|
```javascript
|
|
const router = createEntityRouter(
|
|
'tour_pages', // Entity name
|
|
Tour_pagesService, // Service class
|
|
Tour_pagesDBApi, // Database API class
|
|
{
|
|
permissionEntity: 'tour_pages', // Permission entity name (optional)
|
|
csvFields: ['id', 'name'], // CSV export fields (optional)
|
|
validation: { // Request validation overrides (optional)
|
|
create: customCreateSchema,
|
|
update: customUpdateSchema,
|
|
},
|
|
customRoutes: (router, Service, DBApi) => { // Custom routes (optional)
|
|
router.post('/custom', handler);
|
|
}
|
|
}
|
|
);
|
|
```
|
|
|
|
#### Generated Endpoints
|
|
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| `POST` | `/` | Create new item |
|
|
| `POST` | `/bulk-import` | Bulk import items |
|
|
| `PUT` | `/:id` | Update item by ID |
|
|
| `DELETE` | `/:id` | Delete item by ID |
|
|
| `POST` | `/deleteByIds` | Delete multiple items |
|
|
| `GET` | `/` | List items (with pagination, filters) |
|
|
| `GET` | `/count` | Count items matching filters |
|
|
| `GET` | `/autocomplete` | Autocomplete search |
|
|
| `GET` | `/:id` | Get single item by ID |
|
|
|
|
#### Factory Features
|
|
|
|
**Permission Checking:**
|
|
```javascript
|
|
router.use(checkCrudPermissions(permissionEntity));
|
|
// Maps HTTP methods to permissions:
|
|
// GET → READ_ENTITY
|
|
// POST → CREATE_ENTITY
|
|
// PUT/PATCH → UPDATE_ENTITY
|
|
// DELETE → DELETE_ENTITY
|
|
```
|
|
|
|
**Request Validation:**
|
|
```typescript
|
|
import { validateRequest } from '../middlewares/validate-request.ts';
|
|
import { crud as crudSchemas } from '../validators/request-schemas.ts';
|
|
|
|
router.get(
|
|
'/:id',
|
|
validateRequest(crudSchemas.findOne),
|
|
wrapAsync(async (req, res) => {
|
|
const payload = await DBApi.findBy({ id: req.params.id });
|
|
res.status(200).send(payload);
|
|
}),
|
|
);
|
|
```
|
|
|
|
Factory CRUD routes validate request bodies, params, and common query controls before service/DB calls. List and count routes validate `limit`, `page`, `field`, `sort`, and `filetype` while keeping existing entity filter query parameters. Entity routers can pass `validation` overrides for stricter contracts, as `users` and `projects` do.
|
|
|
|
**CSV Export:**
|
|
```javascript
|
|
// GET /?filetype=csv
|
|
if (filetype === 'csv') {
|
|
const csv = parse(payload.rows, { fields });
|
|
res.status(200).attachment('export.csv').send(csv);
|
|
}
|
|
```
|
|
|
|
**Runtime Context:**
|
|
```javascript
|
|
const runtimeContext = req.runtimeContext;
|
|
const payload = await DBApi.findAll(req.query, {
|
|
currentUser,
|
|
runtimeContext,
|
|
});
|
|
```
|
|
|
|
---
|
|
|
|
## Route Mounting (index.js)
|
|
|
|
Routes are mounted with authentication and rate limiting:
|
|
|
|
```javascript
|
|
// No auth required
|
|
app.use('/api/auth', authRoutes);
|
|
app.use('/api/runtime-context', runtimeContextRoutes);
|
|
app.get('/api/health', healthHandler);
|
|
|
|
// File routes (before body parser)
|
|
app.use('/api/file/download', downloadLimiter);
|
|
app.use('/api/file/presign', downloadLimiter);
|
|
app.use('/api/file/upload', uploadLimiter);
|
|
app.use('/api/file', fileRoutes);
|
|
|
|
// JWT auth required
|
|
app.use('/api/users', jwtAuth, usersRoutes);
|
|
app.use('/api/roles', jwtAuth, rolesRoutes);
|
|
app.use('/api/permissions', jwtAuth, permissionsRoutes);
|
|
app.use('/api/project_memberships', jwtAuth, project_membershipsRoutes);
|
|
app.use('/api/assets', jwtAuth, assetsRoutes);
|
|
app.use('/api/asset_variants', jwtAuth, asset_variantsRoutes);
|
|
app.use('/api/presigned_url_requests', jwtAuth, presigned_url_requestsRoutes);
|
|
app.use('/api/publish_events', jwtAuth, publish_eventsRoutes);
|
|
app.use('/api/pwa_caches', jwtAuth, pwa_cachesRoutes);
|
|
app.use('/api/access_logs', jwtAuth, access_logsRoutes);
|
|
app.use('/api/element-type-defaults', jwtAuth, element_type_defaultsRoutes);
|
|
app.use('/api/project-element-defaults', jwtAuth, project_element_defaultsRoutes);
|
|
app.use('/api/publish', jwtAuth, publishRoutes);
|
|
|
|
// JWT + Rate limiting
|
|
app.use('/api/search', jwtAuth, searchLimiter, searchRoutes);
|
|
|
|
// Runtime public access (production environment)
|
|
mountRuntimeEntityRoute('/api/projects', 'projects', projectsRoutes);
|
|
mountRuntimeEntityRoute('/api/tour_pages', 'tour_pages', tour_pagesRoutes);
|
|
mountRuntimeEntityRoute('/api/project_audio_tracks', 'project_audio_tracks', ...);
|
|
```
|
|
|
|
---
|
|
|
|
## Custom Routes Detail
|
|
|
|
### 1. auth.ts (327 lines)
|
|
|
|
Authentication and account management.
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| POST | `/signin/local` | No | Email/password login |
|
|
| POST | `/signup` | No | Register new user |
|
|
| GET | `/me` | JWT | Get current user |
|
|
| PUT | `/password-reset` | No | Reset password with token |
|
|
| PUT | `/password-update` | JWT | Change password |
|
|
| PUT | `/profile` | JWT | Update user profile |
|
|
| PUT | `/verify-email` | No | Verify email with token |
|
|
| POST | `/send-email-address-verification-email` | JWT | Resend verification |
|
|
| POST | `/send-password-reset-email` | No | Send reset email |
|
|
| GET | `/email-configured` | No | Check email config |
|
|
| GET | `/signin/google` | No | Google OAuth start |
|
|
| GET | `/signin/google/callback` | No | Google OAuth callback |
|
|
| GET | `/signin/microsoft` | No | Microsoft OAuth start |
|
|
| GET | `/signin/microsoft/callback` | No | Microsoft OAuth callback |
|
|
|
|
`auth.ts` is a typed ESM route boundary. It uses reusable request body/query contracts from `backend/src/types/auth-routes.ts`, the typed `backend/src/services/auth.ts` service, and the shared Passport helpers in `backend/src/auth/passport-middleware.ts`.
|
|
|
|
---
|
|
|
|
### 2. file.ts (150 lines)
|
|
|
|
File upload and download operations.
|
|
|
|
`file.ts` is a typed ESM route boundary. It calls the typed unified storage facade in `backend/src/services/file.ts`, while provider contracts and request/response shapes live in reusable `backend/src/types/file.ts` contracts. S3 and GCloud use official SDK-provided types instead of local SDK declarations.
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| GET | `/download` | No | Download file by privateUrl |
|
|
| POST | `/presign` | No | Generate presigned URLs (max 50) |
|
|
| POST | `/upload/:table/:field` | JWT | Legacy single file upload |
|
|
| POST | `/upload-sessions/init` | JWT | Initialize chunked upload |
|
|
| GET | `/upload-sessions/:sessionId` | JWT | Get upload session status |
|
|
| PUT | `/upload-sessions/:sessionId/chunks/:chunkIndex` | JWT | Upload chunk |
|
|
| POST | `/upload-sessions/:sessionId/finalize` | JWT | Finalize chunked upload |
|
|
|
|
**Presigned URLs Request:**
|
|
```json
|
|
{
|
|
"urls": ["assets/image.jpg", "assets/video.mp4"]
|
|
}
|
|
```
|
|
|
|
**Presigned URLs Response:**
|
|
```json
|
|
{
|
|
"presignedUrls": {
|
|
"assets/image.jpg": "https://s3.../assets/image.jpg?X-Amz-...",
|
|
"assets/video.mp4": "https://s3.../assets/video.mp4?X-Amz-..."
|
|
}
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 3. publish.ts (107 lines)
|
|
|
|
Publishing workflow for Dev → Stage → Production.
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| POST | `/` | JWT | Publish stage to production |
|
|
| POST | `/publish` | JWT | Alias for publish |
|
|
| POST | `/save-to-stage` | JWT | Save dev to stage |
|
|
|
|
**Publish Request:**
|
|
```json
|
|
{
|
|
"projectId": "uuid",
|
|
"title": "Release 1.0",
|
|
"description": "Initial release"
|
|
}
|
|
```
|
|
|
|
**Save to Stage Request:**
|
|
```json
|
|
{
|
|
"projectId": "uuid"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
### 4. search.ts (64 lines)
|
|
|
|
Global full-text search across entities.
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| POST | `/` | JWT | Search across all entities |
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"searchQuery": "my search term"
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"users": [...],
|
|
"projects": [...],
|
|
"tour_pages": [...]
|
|
}
|
|
```
|
|
|
|
### 5. runtime-context.ts (16 lines)
|
|
|
|
Runtime context inspection for debugging.
|
|
|
|
| Method | Path | Auth | Description |
|
|
|--------|------|------|-------------|
|
|
| GET | `/` | No | Get current runtime context |
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"mode": "admin",
|
|
"projectSlug": null,
|
|
"headerEnvironment": "production",
|
|
"headerProjectSlug": "my-tour"
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Entity Routes Detail
|
|
|
|
### projects.ts (46 lines)
|
|
|
|
Projects with factory CRUD plus a clone endpoint.
|
|
|
|
**Standard CRUD Endpoints:**
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/` | Create project |
|
|
| POST | `/bulk-import` | Bulk import projects |
|
|
| PUT | `/:id` | Update project |
|
|
| DELETE | `/:id` | Delete project |
|
|
| POST | `/deleteByIds` | Delete multiple projects |
|
|
| GET | `/` | List projects |
|
|
| GET | `/count` | Count projects |
|
|
| GET | `/autocomplete` | Autocomplete search |
|
|
| GET | `/:id` | Get project by ID |
|
|
|
|
**Custom Endpoints:**
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/:id/clone` | Clone project with all pages |
|
|
---
|
|
|
|
### tour_pages.ts (380 lines)
|
|
|
|
Typed manual CRUD route for tour pages. In addition to standard create/update/delete/list/count/autocomplete/find-by-id operations, it owns page reorder, dev-page duplication, CSV export, and reverse-video status endpoints. The route uses reusable contracts from `backend/src/types/tour-pages.ts`.
|
|
|
|
**Schema Fields:**
|
|
- `source_key`, `name`, `slug`
|
|
- `background_image_url`, `background_video_url`, `background_audio_url`
|
|
- `ui_schema_json`, `sort_order`
|
|
|
|
**Endpoints:**
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/` | Create page |
|
|
| POST | `/bulk-import` | Bulk import pages |
|
|
| POST | `/reorder` | Reorder dev pages in a project/environment |
|
|
| POST | `/:id/duplicate` | Duplicate a dev page |
|
|
| PUT | `/:id` | Update page |
|
|
| DELETE | `/:id` | Remove page |
|
|
| POST | `/deleteByIds` | Remove multiple pages |
|
|
| GET | `/` | List pages with reverse video URL population and optional CSV export |
|
|
| POST | `/reverse-video-status` | Check generated reverse-video variants |
|
|
| GET | `/count` | Count pages |
|
|
| GET | `/autocomplete` | Page autocomplete |
|
|
| GET | `/:id` | Get one page with reverse video URL population |
|
|
- `POST /api/tour_pages/reorder` accepts `{ data: { projectId, environment,
|
|
orderedPageIds } }`, validates a complete page list for the project/dev
|
|
environment, and updates only `sort_order`.
|
|
- `POST /api/tour_pages/:id/duplicate` accepts `{ data: { projectId,
|
|
environment, name, slug } }`, duplicates a dev page into a new independent dev
|
|
page, appends it to the project order, deep-copies `ui_schema_json`, and
|
|
regenerates inline element IDs.
|
|
- Constructor page deletion uses the standard `DELETE /api/tour_pages/:id`
|
|
route after a frontend confirmation modal; no separate delete endpoint is
|
|
needed.
|
|
- Custom routes are protected by Passport JWT like other tour page mutations and
|
|
are implemented before `/:id` where needed so custom paths are not treated as
|
|
entity IDs.
|
|
- Non-dev environments are rejected for reorder and duplicate; stage and
|
|
production changes only flow through Save to Stage and Publish.
|
|
|
|
---
|
|
|
|
### element_type_defaults.ts (12 lines)
|
|
|
|
Minimal factory usage with permission override.
|
|
|
|
```javascript
|
|
module.exports = createEntityRouter(
|
|
'element_type_defaults',
|
|
Element_type_defaultsService,
|
|
Element_type_defaultsDBApi,
|
|
{
|
|
permissionEntity: 'page_elements', // Uses PAGE_ELEMENTS permissions
|
|
},
|
|
);
|
|
```
|
|
|
|
**URL Aliases:**
|
|
- `/api/element-type-defaults` (primary)
|
|
- `/api/ui-elements` (backwards compatibility)
|
|
|
|
---
|
|
|
|
### project_element_defaults.ts (92 lines)
|
|
|
|
Factory-generated routes plus custom endpoints for resetting and comparing defaults.
|
|
|
|
```javascript
|
|
const baseRouter = createEntityRouter(
|
|
'project_element_defaults',
|
|
Project_element_defaultsService,
|
|
Project_element_defaultsDBApi,
|
|
{
|
|
permissionEntity: 'page_elements',
|
|
},
|
|
);
|
|
|
|
// Add custom endpoints
|
|
baseRouter.post('/:id/reset', ...);
|
|
baseRouter.get('/:id/diff', ...);
|
|
```
|
|
|
|
**Standard CRUD Endpoints:** (via factory)
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/` | Create project element default |
|
|
| PUT | `/:id` | Update project element default |
|
|
| DELETE | `/:id` | Delete project element default |
|
|
| GET | `/` | List project element defaults |
|
|
| GET | `/:id` | Get project element default by ID |
|
|
|
|
**Custom Endpoints:**
|
|
| Method | Path | Description |
|
|
|--------|------|-------------|
|
|
| POST | `/:id/reset` | Reset project element default to global |
|
|
| GET | `/:id/diff` | Get diff from global element type default |
|
|
|
|
**URL Alias:**
|
|
- `/api/project-element-defaults` (primary)
|
|
|
|
---
|
|
|
|
## Request/Response Patterns
|
|
|
|
### List Endpoint (GET /)
|
|
|
|
**Query Parameters:**
|
|
| Param | Type | Description |
|
|
|-------|------|-------------|
|
|
| `page` | number | Page number (0-indexed) |
|
|
| `limit` | number | Items per page |
|
|
| `field` | string | Sort field |
|
|
| `sort` | string | Sort direction (`asc`/`desc`) |
|
|
| `filetype` | string | Export format (`csv`) |
|
|
| `[fieldName]` | string | Filter by field value |
|
|
| `[fieldName]Range` | array | Filter by range `[start, end]` |
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"rows": [...],
|
|
"count": 150
|
|
}
|
|
```
|
|
|
|
### Create Endpoint (POST /)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"data": {
|
|
"name": "New Item",
|
|
"field1": "value1"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"name": "New Item",
|
|
"field1": "value1",
|
|
"createdAt": "2024-01-01T00:00:00.000Z"
|
|
}
|
|
```
|
|
|
|
### Update Endpoint (PUT /:id)
|
|
|
|
**Request:**
|
|
```json
|
|
{
|
|
"id": "uuid",
|
|
"data": {
|
|
"name": "Updated Name"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Response:**
|
|
```json
|
|
true
|
|
```
|
|
|
|
### Delete Endpoints
|
|
|
|
**Single Delete (DELETE /:id):**
|
|
```json
|
|
true
|
|
```
|
|
|
|
**Bulk Delete (POST /deleteByIds):**
|
|
```json
|
|
{
|
|
"data": ["uuid1", "uuid2", "uuid3"]
|
|
}
|
|
```
|
|
|
|
---
|
|
|
|
## Swagger Documentation
|
|
|
|
Swagger UI is served at `GET /api-docs`. The OpenAPI document is maintained in
|
|
`backend/src/openapi/document.ts`, not in route-local JSDoc comments.
|
|
|
|
```javascript
|
|
const specs = createOpenApiDocument({
|
|
serverUrl: config.server.swaggerServerUrl,
|
|
});
|
|
```
|
|
|
|
The OpenAPI module contains:
|
|
|
|
- Shared schemas for auth, users, projects, tour pages, assets, publishing,
|
|
runtime access, file upload, UI controls, transition settings, and audit
|
|
entities.
|
|
- A reusable factory CRUD path generator covering `POST /`, `POST /bulk-import`,
|
|
`PUT /:id`, `DELETE /:id`, `POST /deleteByIds`, `GET /`, `GET /count`,
|
|
`GET /autocomplete`, and `GET /:id`.
|
|
- Explicit path definitions for custom routes such as auth flows, publishing,
|
|
file upload/download, runtime access/context, project clone, tour page reorder
|
|
and duplicate, transition/UI-control settings, and project element default
|
|
reset/diff.
|
|
|
|
When adding routes, update `backend/src/openapi/document.ts` alongside the route
|
|
implementation. For factory-backed entities, add a schema and `CrudResource`
|
|
entry instead of copying per-route Swagger boilerplate.
|
|
|
|
**Access Swagger UI:** `http://localhost:3000/api-docs` in local development.
|
|
|
|
On the standard VM, the backend runs in `NODE_ENV=dev_stage` on port `3000`;
|
|
Apache serves the public domain on port `80`. See
|
|
[`deployment-vm.md`](../../../documentation/deployment-vm.md) for VM health checks.
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### wrapAsync Helper
|
|
|
|
All async route handlers use `wrapAsync` for error propagation:
|
|
|
|
```javascript
|
|
router.post(
|
|
'/',
|
|
wrapAsync(async (req, res) => {
|
|
// Errors thrown here are caught and passed to error handler
|
|
const payload = await Service.create({
|
|
data: req.body.data,
|
|
currentUser: req.currentUser,
|
|
runtimeContext: req.runtimeContext,
|
|
});
|
|
res.status(200).send(payload);
|
|
}),
|
|
);
|
|
```
|
|
|
|
### commonErrorHandler
|
|
|
|
Each route file includes error handler:
|
|
|
|
```javascript
|
|
router.use('/', require('../helpers').commonErrorHandler);
|
|
```
|
|
|
|
**Error Response Mapping:**
|
|
| Status Code | Description |
|
|
|-------------|-------------|
|
|
| 400 | Bad Request |
|
|
| 401 | Unauthorized |
|
|
| 403 | Forbidden |
|
|
| 404 | Not Found |
|
|
| 409 | Conflict |
|
|
| 422 | Unprocessable Entity |
|
|
| 500 | Internal Server Error |
|
|
|
|
---
|
|
|
|
## Route Categories
|
|
|
|
### Public Routes (No Auth)
|
|
|
|
| Route | Description |
|
|
|-------|-------------|
|
|
| `GET /api/health` | Health check |
|
|
| `POST /api/auth/signin/local` | Login |
|
|
| `GET /api/auth/signin/google` | Google OAuth |
|
|
| `GET /api/auth/signin/microsoft` | Microsoft OAuth |
|
|
| `GET /api/file/download` | File download |
|
|
| `POST /api/file/presign` | Generate presigned URLs |
|
|
| `GET /api/runtime-context` | Runtime context |
|
|
|
|
### Runtime Public Routes (Production Environment)
|
|
|
|
| Route | Description |
|
|
|-------|-------------|
|
|
| `GET /api/projects` | List projects (sanitized) |
|
|
| `GET /api/tour_pages` | List tour pages (sanitized) |
|
|
| `GET /api/project_audio_tracks` | List audio tracks (sanitized) |
|
|
|
|
### Authenticated Routes (JWT Required)
|
|
|
|
All other routes require JWT authentication via:
|
|
```javascript
|
|
app.use('/api/users', jwtAuth, usersRoutes);
|
|
```
|
|
|
|
### Rate Limited Routes
|
|
|
|
| Route | Limiter | Config |
|
|
|-------|---------|--------|
|
|
| `/api/auth/signin/local` | authLimiter | 10/15min |
|
|
| `/api/auth/send-password-reset-email` | passwordResetLimiter | 5/hour |
|
|
| `/api/file/upload*` | uploadLimiter | 10/min |
|
|
| `/api/file/download`, `/presign` | downloadLimiter | 200/min |
|
|
| `/api/search` | searchLimiter | 30/min |
|
|
|
|
## Dependencies
|
|
|
|
| Package | Purpose |
|
|
|---------|---------|
|
|
| `express` | Router and middleware |
|
|
| `json2csv` | CSV export functionality |
|
|
| `passport` | JWT authentication |
|
|
| `body-parser` | JSON body parsing |
|
|
|
|
---
|
|
|
|
## Testing
|
|
|
|
### Test Entity CRUD
|
|
|
|
```bash
|
|
# Create
|
|
curl -X POST http://localhost:3000/api/projects \
|
|
-H "Authorization: Bearer <JWT>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"data": {"name": "Test Project", "slug": "test"}}'
|
|
|
|
# List
|
|
curl http://localhost:3000/api/projects \
|
|
-H "Authorization: Bearer <JWT>"
|
|
|
|
# Get by ID
|
|
curl http://localhost:3000/api/projects/<uuid> \
|
|
-H "Authorization: Bearer <JWT>"
|
|
|
|
# Update
|
|
curl -X PUT http://localhost:3000/api/projects/<uuid> \
|
|
-H "Authorization: Bearer <JWT>" \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"id": "<uuid>", "data": {"name": "Updated Name"}}'
|
|
|
|
# Delete
|
|
curl -X DELETE http://localhost:3000/api/projects/<uuid> \
|
|
-H "Authorization: Bearer <JWT>"
|
|
```
|
|
|
|
### Test Public Runtime
|
|
|
|
```bash
|
|
curl http://localhost:3000/api/projects \
|
|
-H "X-Runtime-Environment: production"
|
|
```
|
|
|
|
### Test Presigned URLs
|
|
|
|
```bash
|
|
curl -X POST http://localhost:3000/api/file/presign \
|
|
-H "Content-Type: application/json" \
|
|
-d '{"urls": ["assets/test.jpg"]}'
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
The Routes module provides:
|
|
|
|
1. **Factory Pattern** - 12 entities with standardized CRUD (createEntityRouter)
|
|
2. **Hybrid Pattern** - 2 entities with factory CRUD + custom endpoints (projects, project_element_defaults)
|
|
3. **Custom Routes** - 7 specialized route files for auth, files, search, etc.
|
|
4. **Swagger Documentation** - centralized OpenAPI document for all endpoints
|
|
5. **Permission Checking** - RBAC via checkCrudPermissions middleware
|
|
6. **Runtime Public Access** - Production environment public read access
|
|
7. **Rate Limiting** - Per-endpoint rate limiting
|
|
8. **CSV Export** - Export capability via `?filetype=csv`
|
|
9. **Request Validation** - Joi schemas via validateRequest before service calls
|
|
10. **Error Handling** - Centralized via wrapAsync and commonErrorHandler
|
|
|
|
**Route Statistics:**
|
|
- 26 route files
|
|
- ~50+ unique endpoints
|
|
- 11 factory-generated entity routers
|
|
- 3 hybrid entity routers (factory + custom)
|
|
- 7 custom route implementations
|