39948-vm/documentation/project-architecture.md
2026-07-03 16:11:24 +02:00

41 KiB

Project Architecture - E2E Documentation

Overview

Tour Builder Platform - A sophisticated web application for building and managing virtual tours with interactive pages, assets, transitions, and offline capabilities.

Technology Stack:

  • Frontend: Next.js 15 with React 19, TypeScript, Redux Toolkit, Tailwind CSS, Serwist PWA
  • Backend: Node.js/Express with Sequelize ORM
  • Database: PostgreSQL with migrations and seeders
  • File Storage: AWS S3 (configurable via FILE_STORAGE_PROVIDER)

High-Level Architecture

┌─────────────────────────────────────────────────────────────────┐
│                         Frontend                                 │
│  Next.js 15 │ React 19 │ Redux Toolkit │ Tailwind │ Serwist    │
└─────────────────────────────────────────────────────────────────┘
                              │
                         HTTP/REST
                              │
┌─────────────────────────────────────────────────────────────────┐
│                         Backend                                  │
│  Express.js │ Passport.js │ Sequelize ORM │ Factory Patterns    │
└─────────────────────────────────────────────────────────────────┘
                              │
                         Sequelize
                              │
┌─────────────────────────────────────────────────────────────────┐
│                        Database                                  │
│  PostgreSQL │ Migrations │ Seeders                              │
└─────────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────────┐
│                      File Storage                                │
│  AWS S3 │ Google Cloud Storage │ Local Filesystem               │
└─────────────────────────────────────────────────────────────────┘

Standard VM Deployment

The standard Flatlogic VM adds Apache, PM2, and executor services around the application:

Cloudflare
  -> Apache :80
    -> Next.js frontend PM2 app `frontend-dev` :3001 (production server)
    -> Express backend PM2 app `backend-dev` :3000 (NODE_ENV=dev_stage)

PM2 systemd unit: pm2-ubuntu.service
Auxiliary PM2 apps: fl-executor, fl-telemetry

Local development now mirrors this port split: frontend 3001, backend 3000. A protected backend endpoint returning 401 Unauthorized is a healthy response when no JWT is supplied.

For the detailed VM runbook, OOM recovery, process checks, and executor notes, see deployment-vm.md.


Frontend Architecture

Directory Structure

frontend/src/
├── pages/              # Next.js pages (dynamic routing)
├── components/         # React components (PascalCase naming)
├── stores/             # Redux Toolkit slices (entity-based)
├── layouts/            # Page layouts (Authenticated, Guest)
├── hooks/              # Custom React hooks (~32 hooks)
├── lib/                # Utility libraries (offline, logging)
├── types/              # TypeScript interfaces and types
├── helpers/            # Utility functions
├── css/                # Global CSS (Tailwind)
├── config/             # App configuration
├── context/            # React context providers
├── schemas/            # Zod validation schemas
└── factories/          # Page and component factories

Pages Structure

Page Type Pattern Description
List {entity}-list.tsx Data grid with CRUD, filtering, sorting
Edit {entity}-edit.tsx Form-based entity modification
View {entity}-view.tsx Read-only entity display
New {entity}-new.tsx Create new entity form

Special Pages:

  • constructor.tsx - Visual tour builder (complex)
  • p/[projectSlug]/index.tsx - Production tour presentation
  • p/[projectSlug]/stage.tsx - Stage tour preview
  • login.tsx - Authentication page
  • dashboard.tsx - Overview and analytics

State Management (Redux Toolkit)

Store Configuration (stores/store.ts):

// Entity slices (13 total)
users, roles, permissions, projects, assets, tour_pages,
asset_variants, project_audio_tracks, publish_events, pwa_caches,
access_logs, presigned_url_requests, project_memberships

// Global slices
authSlice      // Authentication state, JWT token
mainSlice      // UI state, notifications
styleSlice     // Theme, styling preferences

Redux Pattern:

// Entity slice factory
import { createEntitySlice } from './createEntitySlice';

const assetsSlice = createEntitySlice('assets', {
  // Custom reducers if needed
});

// Usage in components
import { fetch, create, update, deleteItem } from '@/stores/assets/assetsSlice';
import { useAppDispatch, useAppSelector } from '@/stores/hooks';

const dispatch = useAppDispatch();
const { rows, loading } = useAppSelector((state) => state.assets);

dispatch(fetch({ query: '?limit=100&page=1' }));

Component Organization

components/
├── Assets/
│   ├── AssetCard.tsx
│   ├── AssetGrid.tsx
│   ├── useAssetUploader.ts      # Custom hook
│   └── ProjectSelector.tsx
├── Offline/
│   ├── OfflineToggle.tsx
│   ├── OfflineStatusIndicator.tsx
│   └── DownloadProgressPanel.tsx
├── Layout/
│   ├── Header.tsx
│   ├── Sidebar.tsx
│   └── Footer.tsx
└── [Entity]/
    ├── [Entity]Card.tsx
    ├── [Entity]Form.tsx
    └── [Entity]List.tsx

Custom Hooks

Hook Location Purpose
useAssetUploader components/Assets/ Chunked file upload management
usePreloadOrchestrator hooks/ Asset preloading with S3 presigned URLs and ready blob URLs
usePageSwitch hooks/ Smooth page navigation with preloaded blob URLs
useTransitionPlayback hooks/ Transition video playback coordination
useNeighborGraph hooks/ Navigation graph for preloading (maxDepth: 1)
useNetworkAware hooks/ Network condition detection
useReversePlayback hooks/ Video reverse playback
useOfflineMode hooks/ Offline download management
useStorageQuota hooks/ Storage quota monitoring

TypeScript Types

types/
├── api.ts          # PaginatedResponse, FetchParams, ApiError
├── entities.ts     # BaseEntity, User, Role, Project, Asset, etc.
├── redux.ts        # Entity slice configuration
├── constructor.ts  # Tour builder specific types
├── runtime.ts      # Runtime environment types
├── offline.ts      # PWA and offline types
├── preload.ts      # Preloading system types
├── permissions.ts  # Permission enum definitions
├── presentation.ts # Presentation runtime types
├── filters.ts      # Filter system types
├── forms.ts        # Form field types
└── index.ts        # Central exports

Frontend Configuration

// config/config.ts
const config = {
  apiBaseUrl: process.env.NEXT_PUBLIC_BACK_API || 'http://localhost:3000/api',
  appTitle: 'Shimahara Visual',
  storageKeys: {
    darkMode: 'tour-builder-dark-mode',
    stylePrefs: 'tour-builder-style',
  },
};

Build Configuration

// next.config.mjs
{
  // PWA with Serwist
  swSrc: 'src/sw.ts',
  swDest: 'public/sw.js',
  disable: process.env.NODE_ENV === 'development',

  // Next.js settings
  trailingSlash: true,
  distDir: process.env.NODE_ENV === 'development' ? '.next' : 'build',
  output: process.env.NEXT_OUTPUT || undefined,
  images: { unoptimized: true },

  // Development
  experimental: { turbo: true }
}

Backend Architecture

Directory Structure

backend/src/
├── routes/             # Express route handlers (22 routes)
├── db/
│   ├── models/         # Sequelize model definitions (16 models)
│   ├── api/            # Database access layer (GenericDBApi)
│   ├── migrations/     # Database schema migrations
│   ├── seeders/        # Seed data scripts
│   ├── db-config.ts    # Database configuration
│   └── umzug.ts        # Migration and seeder runner
├── services/           # Business logic services
├── auth/               # Passport.js strategies
├── middlewares/        # Express middlewares
├── factories/          # Factory patterns
└── utils/              # Logger, error handling

Three-Tier Architecture

┌─────────────────────────────────────────────────────────────────┐
│                        Routes Layer                              │
│  Express handlers │ Request validation │ Response formatting    │
└─────────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────────┐
│                       Services Layer                             │
│  Business logic │ Transactions │ External integrations          │
└─────────────────────────────────────────────────────────────────┘
                              │
┌─────────────────────────────────────────────────────────────────┐
│                        DB API Layer                              │
│  Sequelize queries │ Filtering │ Associations │ Transformations │
└─────────────────────────────────────────────────────────────────┘

Factory Patterns

Router Factory (factories/router.factory.js):

// Generates standard CRUD routes automatically
module.exports = createEntityRouter('assets', AssetsService, AssetsDBApi, {
  permissionEntity: 'assets',
  csvFields: ['name', 'url', 'type'],
  customRoutes: (router) => {
    router.get('/custom', customHandler);
  }
});

// Generated routes:
// GET    /api/assets          - List with pagination/filtering
// GET    /api/assets/:id      - Get single entity
// POST   /api/assets          - Create entity
// PUT    /api/assets/:id      - Update entity
// DELETE /api/assets/:id      - Delete entity
// POST   /api/assets/bulk-import   - CSV import
// DELETE /api/assets/deleteByIds   - Bulk delete

Service Factory (factories/service.factory.js):

// Creates service class with transaction handling
module.exports = createEntityService('assets', AssetsDBApi);

// Generated methods:
// create({ data, currentUser, transaction, runtimeContext }) - Create with transaction
// update({ id, data, currentUser, transaction, runtimeContext }) - Update with transaction
// remove({ id, currentUser, transaction, runtimeContext }) - Delete with transaction
// bulkImport(data, options) - Batch import
// deleteByIds({ ids, currentUser, transaction, runtimeContext }) - Batch delete

GenericDBApi (db/api/base.api.js):

class GenericDBApi {
  // Override in entity APIs
  MODEL = null;
  SEARCHABLE_FIELDS = [];      // Text search fields
  RANGE_FIELDS = [];           // Numeric/date range filters
  ENUM_FIELDS = [];            // Exact match filters
  ASSOCIATIONS = [];           // Related entity loading

  // Transform input before save
  getFieldMapping(data) { return data; }

  // Standard operations
  findAll(query, options) { }
  findOne(id, options) { }
  create(data, options) { }
  update({ id, data, currentUser, transaction, runtimeContext }) { }
  remove({ id, currentUser, transaction, runtimeContext }) { }
}

Middleware Stack

// Applied in order
app.use(helmet());                    // Security headers
app.use(cors({ origin: true }));      // CORS
app.use(requestLogger);               // Request logging
app.use(runtimeContext);              // Admin/stage/production detection
app.use(bodyParser.json());           // JSON parsing
app.use(passport.initialize());       // Auth initialization

// Per-route middleware
router.use(passport.authenticate('jwt'));  // JWT validation
router.use(checkPermissions);              // RBAC check

Authentication System

Passport Strategies:

// 1. JWT Strategy (primary)
passport.use(new JwtStrategy({
  jwtFromRequest: ExtractJwt.fromAuthHeaderAsBearerToken(),
  secretOrKey: process.env.SECRET_KEY,
}, async (payload, done) => {
  const user = await findUserByEmail(payload.email);
  if (user && !user.disabled) {
    return done(null, user);
  }
  return done(null, false);
}));

// 2. Google OAuth 2.0
passport.use(new GoogleStrategy({
  clientID: process.env.GOOGLE_CLIENT_ID,
  clientSecret: process.env.GOOGLE_CLIENT_SECRET,
  callbackURL: '/api/auth/signin/google/callback',
}, findOrCreateUser));

// 3. Microsoft OAuth
passport.use(new MicrosoftStrategy({...}, findOrCreateUser));

JWT Configuration:

  • Secret: process.env.SECRET_KEY
  • Expiration: 6 hours
  • Payload: { id, email, name }

Runtime Context System

The platform uses route-based environment access (not subdomains):

Route Environment Purpose
/p/[slug] production Public presentation
/p/[slug]/stage stage Preview presentation
/constructor?projectId= dev Editor (always dev)

Middleware (middleware/runtime-context.ts):

// Detects context from hostname AND headers
req.runtimeContext = {
  mode: detectFromHostname(hostname),     // 'admin' | 'stage' | 'production'
  projectSlug: fromHostname || null,
  headerEnvironment: req.headers['x-runtime-environment'],  // Route-based
  headerProjectSlug: req.headers['x-runtime-project-slug'], // Route-based
};

Environment Resolution (db/api/runtime-context.ts):

// Priority: hostname-based → header-based
// SECURITY: Only 'production' and 'stage' allowed from headers
// 'dev' from header is ignored to prevent unauthorized access

Frontend Filtering (RuntimePresentation.tsx):

// STRICT: Only exact environment match
.filter((p) => p.environment === environment)

See Publishing Workflow for complete environment isolation details.

Permissions System

// middlewares/check-permissions.ts
const checkPermissions = async (req, res, next) => {
  // 1. Self-access bypass
  if (req.params.id === req.currentUser.id) {
    return next();
  }

  // 2. Check custom permissions
  const customPermission = await findCustomPermission(
    req.currentUser.id,
    req.permissionEntity,
    req.method
  );
  if (customPermission) return next();

  // 3. Check role-based permissions
  const rolePermission = await findRolePermission(
    req.currentUser.app_role,
    req.permissionEntity,
    req.method
  );
  if (rolePermission) return next();

  return res.status(403).json({ error: 'Forbidden' });
};

File Storage System

// Configurable via FILE_STORAGE_PROVIDER env var
const providers = {
  s3: new S3StorageProvider({
    bucket: process.env.AWS_S3_BUCKET,
    region: process.env.AWS_S3_REGION,
  }),
  gcloud: new GCloudStorageProvider({...}),
  local: new LocalStorageProvider({
    basePath: './uploads',
  }),
};

// File service with chunked upload support
class FileService {
  async initUpload(filename, totalChunks) { }
  async uploadChunk(sessionId, chunkIndex, data) { }
  async finalizeUpload(sessionId) { }
  async getDownloadUrl(fileKey) { }
}

S3 Presigned URLs for Direct Download:

The platform supports direct asset downloads from S3 for better performance:

// POST /api/file/presign - Get presigned URLs for batch download
// Request: { urls: ["assets/image1.jpg", "assets/video.mp4", ...] }
// Response: { presignedUrls: { "assets/image1.jpg": "https://s3...", ... } }
// - Max 50 URLs per request
// - 1-hour expiry
// - Public endpoint (no auth required for runtime)

Frontend preloading uses presigned URLs:

  1. Request presigned URLs via POST /api/file/presign
  2. Download directly from S3 (bypasses backend)
  3. Store in Cache API (dual key: download URL + storage key)
  4. Create blob URL for instant playback
  5. Store in readyBlobUrlsRef (dual key: download URL + storage key) for O(1) lookup

Storage Key Mapping: Assets are cached under both download URL and canonical storage key (e.g., assets/project/video.mp4). This ensures cache hits even when presigned URL signatures change.


Database Schema

Core Domain Models

┌─────────────────────────────────────────────────────────────────┐
│                    Element Defaults Hierarchy                    │
│                                                                  │
│  ┌─────────────────────┐                                        │
│  │ element_type_defaults│ ─── Global platform-wide defaults     │
│  └──────────┬──────────┘                                        │
│             │ (auto-snapshot on project creation)               │
│             ▼                                                   │
│  ┌─────────────────────┐                                        │
│  │project_element_defaults│ ─── Project-specific overrides      │
│  └──────────┬──────────┘                                        │
│             │ (applies defaults to new elements)                │
│             ▼                                                   │
│  ┌─────────────────────┐                                        │
│  │   Page Elements     │ ─── Instance-specific on pages         │
│  └─────────────────────┘                                        │
└─────────────────────────────────────────────────────────────────┘

┌─────────────────┐     ┌─────────────────┐
│    Projects     │────<│   Tour Pages    │
└─────────────────┘     └─────────────────┘
        │                       │
        │                       ▼
        │               ┌─────────────────────────────────────┐
        │               │       ui_schema_json (inline)       │
        │               │  • Page Elements                    │
        │               │  • Navigation Links (targetPageSlug)│
        │               │  • Transition Videos                │
        │               └─────────────────────────────────────┘
        ▼
┌─────────────────┐
│     Assets      │
└─────────────────┘
        │
        ▼
┌─────────────────┐
│ Asset Variants  │
└─────────────────┘

Note: Page elements, navigation links, and transition configurations
are stored inline in tour_pages.ui_schema_json (not separate tables).
Navigation uses targetPageSlug for cross-environment consistency.

Entity Relationships

Entity Relationships
Projects hasMany: tour_pages, assets, project_audio_tracks, publish_events, project_element_defaults, project_memberships, presigned_url_requests, pwa_caches, access_logs
Tour Pages belongsTo: project; contains ui_schema_json with elements, navigation (targetPageSlug), and transitions (transitionVideoUrl)
Assets belongsTo: project; hasMany: asset_variants
Users belongsTo: role; hasMany: custom_permissions
Element Type Defaults hasMany: project_element_defaults (as source_element)
Project Element Defaults belongsTo: project, element_type_defaults (as source_element)

Note: Page elements, navigation links, and transitions are stored inline in tour_pages.ui_schema_json rather than as separate tables. Navigation uses targetPageSlug (not UUIDs) for cross-environment consistency during publishing.

Common Fields Pattern

All entities include:

{
  id: UUID,                 // Primary key
  createdAt: TIMESTAMP,     // Creation timestamp
  updatedAt: TIMESTAMP,     // Last update timestamp
  deletedAt: TIMESTAMP,     // Soft delete (paranoid)
  createdBy: UUID,          // User who created
  updatedBy: UUID,          // User who last updated
}

Key Models

Projects:

{
  id, name, slug, description,
  logo_url, favicon_url, og_image_url,
}

Tour Pages:

{
  id, name, slug, sort_order,
  projectId, environment,
  background_image_url,
  background_video_url,
  background_audio_url,
  background_loop,
  requires_auth,
  ui_schema_json,            // Page layout/elements
}

Page Elements:

{
  id, element_type, name, sort_order,  // element_type is TEXT (not ENUM)
  pageId,
  is_visible,
  x_percent, y_percent,      // Position as percentage (0-100)
  width_percent, height_percent,
  rotation_deg,
  content_json,              // Element-specific data
  style_json,                // CSS properties
}

Element Type Defaults (Global):

{
  id, element_type, name, sort_order,  // Unique element_type per platform
  default_settings_json,      // JSON-serialized default settings
  // 11 predefined types: navigation_next, navigation_prev, spot, tooltip,
  // description, gallery, carousel, logo, video_player, audio_player, popup
}

Project Element Defaults:

{
  id, element_type, name, sort_order,
  projectId,
  settings_json,              // Project-specific settings override
  source_element_id,          // FK to element_type_defaults (for tracking)
  snapshot_version,           // Version for "check for updates" feature
  // Auto-snapshotted from global defaults on project creation
}

Assets:

{
  id, name, projectId,
  asset_type,                // 'image' | 'video' | 'audio' | 'file'
  type,                      // 'general' | 'icon' | 'background_image' | 'audio' | 'video' | 'transition' | 'logo' | 'favicon' | 'document'
  cdn_url, storage_key, mime_type,
  size_mb, width_px, height_px, duration_sec,
  checksum, is_public,
}

Navigation Elements (stored in ui_schema_json):

// Navigation elements with transitions are stored inline in tour_pages.ui_schema_json
{
  id, element_type,  // 'navigation_next', 'navigation_prev', 'spot', etc.
  targetPageSlug,    // Target page for navigation (not UUID)
  transitionVideoUrl,          // Forward transition video
  transitionDurationSec,       // Duration in seconds
  transitionReverseMode,       // 'auto_reverse' | 'separate_video' (replaces supportsReverse)
  reverseVideoUrl,             // Separate reverse video (when mode is 'separate_video')
  // ...other element properties
}

Note: There is no separate transitions table. Transition data is stored directly on navigation elements within each page's ui_schema_json.


Data Flow

Authentication Flow

┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│   Login Page    │────>│  POST /signin   │────>│  Validate Creds │
└─────────────────┘     └─────────────────┘     └─────────────────┘
                                                        │
                                                        ▼
┌─────────────────┐     ┌─────────────────┐     ┌─────────────────┐
│  Store Token    │<────│  Return JWT     │<────│  Generate JWT   │
│  (sessionStorage)│     │                 │     │  (6h expiry)    │
└─────────────────┘     └─────────────────┘     └─────────────────┘
        │
        ▼
┌─────────────────────────────────────────────────────────────────┐
│  Axios Interceptor attaches token to all subsequent requests    │
│  Authorization: Bearer <token>                                  │
└─────────────────────────────────────────────────────────────────┘

CRUD Operation Flow

┌─────────────────┐
│ Redux dispatch  │
│ fetch({query})  │
└────────┬────────┘
         │
         ▼
┌─────────────────┐     ┌─────────────────┐
│   Axios GET     │────>│  JWT Middleware │
│  /api/entity    │     │  (validate)     │
└─────────────────┘     └────────┬────────┘
                                 │
                                 ▼
┌─────────────────┐     ┌─────────────────┐
│  Permissions    │<────│  Route Handler  │
│  Middleware     │     │                 │
└────────┬────────┘     └─────────────────┘
         │
         ▼
┌─────────────────┐     ┌─────────────────┐
│    Service      │────>│    DB API       │
│  (transactions) │     │  (Sequelize)    │
└─────────────────┘     └────────┬────────┘
                                 │
                                 ▼
┌─────────────────┐     ┌─────────────────┐
│   PostgreSQL    │────>│  JSON Response  │
│    Query        │     │  {rows, count}  │
└─────────────────┘     └────────┬────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────┐
│  Redux slice updates state              │
│  Components re-render with new data     │
└─────────────────────────────────────────┘

Asset Upload Flow

┌─────────────────┐     ┌─────────────────┐
│ useAssetUploader│────>│  Split into     │
│  hook           │     │  chunks (5MB)   │
└─────────────────┘     └────────┬────────┘
                                 │
                                 ▼
┌─────────────────┐     ┌─────────────────┐
│  POST /init     │────>│  Create upload  │
│  (get sessionId)│     │  session        │
└─────────────────┘     └────────┬────────┘
                                 │
                                 ▼
┌─────────────────┐     ┌─────────────────┐
│  POST /chunk    │────>│  Store chunk    │
│  (each chunk)   │     │  (S3/local)     │
└─────────────────┘     └────────┬────────┘
                                 │ (repeat)
                                 ▼
┌─────────────────┐     ┌─────────────────┐
│ POST /finalize  │────>│  Combine chunks │
│                 │     │  Create Asset   │
└─────────────────┘     └────────┬────────┘
                                 │
                                 ▼
┌─────────────────────────────────────────┐
│  Return asset metadata with CDN URL     │
└─────────────────────────────────────────┘

Asset Preloading Flow

┌─────────────────────────────────────────────────────────────────┐
│  1. Page Load - Initialize preloading                            │
│     └── usePreloadOrchestrator initializes                       │
│     └── extractPageLinksAndElements(pages) extracts assets       │
│     └── useNeighborGraph builds navigation graph (maxDepth: 1)   │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  2. Batch presigned URL request                                  │
│     └── POST /api/file/presign { urls: [...] }                   │
│     └── Returns S3 presigned URLs (1-hour expiry, max 50)        │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  3. Priority queue downloads (3 concurrent)                      │
│     └── Transition: +150 │ Image: +100 │ Audio: +50 │ Video: +30│
│     └── Current page: 1000 base │ Neighbor: 500 base            │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  4. Download directly from S3                                    │
│     └── fetch(presignedUrl)                                      │
│     └── Store in Cache API (<5MB) or IndexedDB (≥5MB)           │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  5. Create ready blob URL (dual key storage)                     │
│     └── URL.createObjectURL(blob)                                │
│     └── Decode image (if applicable)                             │
│     └── Store in readyBlobUrlsRef Map:                          │
│         • key: downloadUrl → blobUrl                             │
│         • key: storageKey → blobUrl (canonical, never changes)  │
└─────────────────────────────────────────────────────────────────┘
                              │
                              ▼
┌─────────────────────────────────────────────────────────────────┐
│  6. Navigation - Instant asset display                           │
│     └── usePageSwitch.switchToPage(targetPage)                   │
│     └── getReadyBlobUrl(storageKey) → O(1) instant lookup        │
│     └── Smooth page transition without black flash               │
└─────────────────────────────────────────────────────────────────┘

API Patterns

Standard Endpoints

Every entity follows this pattern:

Method Endpoint Description
GET /api/{entity} List with pagination
GET /api/{entity}/:id Get single entity
POST /api/{entity} Create entity
PUT /api/{entity}/:id Update entity
DELETE /api/{entity}/:id Delete entity
POST /api/{entity}/bulk-import CSV import
DELETE /api/{entity}/deleteByIds Bulk delete

Query Parameters

# Pagination
?limit=100&page=1&offset=0

# Sorting
?sortField=createdAt&sortOrder=DESC

# Text search (SEARCHABLE_FIELDS)
?search=keyword

# Range filters (RANGE_FIELDS)
?createdAtRange=2024-01-01,2024-12-31
?sizeRange=0,1000000

# Enum filters (ENUM_FIELDS)
?type=image&status=active

# Export
?filetype=csv

Response Format

// List response
{
  rows: [...],           // Array of entities
  count: 100,            // Total count (for pagination)
}

// Single entity
{
  id: "uuid",
  ...entityFields,
  createdAt: "2024-01-01T00:00:00Z",
  updatedAt: "2024-01-01T00:00:00Z",
}

// Error response
{
  error: "Error message",
  details: {...},        // Optional
}

Environment Configuration

Backend (.env)

# Database
DB_NAME=app_39215
DB_USER=app_39215
DB_PASS=password
DB_HOST=localhost
DB_PORT=5432

# Authentication
SECRET_KEY=jwt-secret-key
GOOGLE_CLIENT_ID=...
GOOGLE_CLIENT_SECRET=...
MS_CLIENT_ID=...
MS_CLIENT_SECRET=...

# File Storage (S3)
FILE_STORAGE_PROVIDER=s3
AWS_ACCESS_KEY_ID=...
AWS_SECRET_ACCESS_KEY=...
AWS_S3_BUCKET=tour-builder-assets
AWS_S3_REGION=us-east-1
AWS_S3_PREFIX=uploads/

# Email (AWS SES)
EMAIL_USER=...
EMAIL_PASS=...

PEXELS_KEY=...

# Runtime
# Optional. If omitted by backend scripts, src/load-env.ts defaults to dev_stage.
NODE_ENV=dev_stage

Frontend (.env.local)

NEXT_PUBLIC_BACK_API=http://localhost:3000/api

Database Configuration

Database configuration lives in backend/src/db/db-config.ts. Database commands are executed by the typed Umzug runner in backend/src/db/umzug.ts. backend/src/load-env.ts loads backend/.env for app and DB entrypoints; if NODE_ENV is absent it defaults to dev_stage, matching the standard VM backend process.


Key Files Reference

Frontend

File Purpose
pages/_app.tsx App wrapper, providers, axios interceptor
stores/store.ts Redux store configuration
stores/createEntitySlice.ts Entity slice factory
config/config.ts App configuration
config/preload.config.ts Preload priorities and settings
next.config.mjs Next.js + Serwist config
src/sw.ts Service worker
hooks/usePreloadOrchestrator.ts Asset preloading with S3 presigned URLs
hooks/usePageSwitch.ts Page navigation with preloaded blob URLs
lib/extractPageLinks.ts Extract navigation links from ui_schema_json
lib/offline/StorageManager.ts Cache API and IndexedDB storage
components/RuntimePresentation.tsx Runtime with environment filtering

Backend

File Purpose
index.ts Express app setup, middleware stack
factories/router.factory.ts CRUD route generator
factories/service.factory.ts Service class generator
db/api/base.api.ts GenericDBApi base class
db/api/element_type_defaults.ts Global element defaults with auto-seed
db/api/project_element_defaults.ts Project element defaults with snapshot
services/publish.ts Dev → Stage → Production publishing
auth/auth.ts Passport strategies
middlewares/check-permissions.ts RBAC middleware

Common Commands

Backend

cd backend

# Install dependencies
npm install

# Start server (port 3000 in dev_stage)
npm run start-dev

# Database operations
npm run db:create    # Create database
npm run db:migrate   # Run migrations
npm run db:seed      # Seed data
npm run db:drop      # Drop database

# Linting
npm run lint

Frontend

cd frontend

# Install dependencies
npm install

# Development server (port 3001)
npm run dev

# Production build
npm run build

# Linting and formatting
npm run lint
npm run format

Docker

cd docker

# First time setup
chmod +x start-backend.sh wait-for-it.sh

# Start all services
docker-compose up

# Fresh database
rm -rf data && docker-compose up

Security Considerations

  1. JWT Authentication - 6-hour expiration, secure secret key
  2. RBAC - Role-based access control with custom overrides
  3. Helmet - Security headers (XSS, CSRF protection)
  4. CORS - Configured for specific origins
  5. SQL Injection - Prevented via Sequelize parameterized queries
  6. Soft Delete - Paranoid mode preserves audit trail
  7. File Validation - MIME type and size checks on upload

Performance Optimizations

  1. Chunked Uploads - Prevents memory overload for large files
  2. Pagination - All list endpoints support limit/offset
  3. Lazy Loading - Redux thunks load data on demand
  4. Asset Variants - Multiple quality levels (thumbnail, preview, etc.)
  5. PWA Caching - Service worker caches static assets
  6. S3 Direct Download - Assets downloaded directly from S3 via presigned URLs (bypasses backend)
  7. Ready Blob URLs - Pre-created blob URLs stored in memory for O(1) instant lookup (getReadyBlobUrl)
  8. Storage Key Mapping - Assets cached by canonical storage key (e.g., assets/vid.mp4) enabling cache hits regardless of presigned URL signature changes
  9. Neighbor Preloading - BFS traversal preloads immediate neighbors (maxDepth: 1)
  10. Priority Queue - Transitions (+150), images (+100), audio (+50), video (+30)
  11. IndexedDB - Large files (≥5MB) stored locally for offline access
  12. Cache API - Small files (<5MB) stored in browser Cache API
  13. Blob URL Rendering - Native <img> tags for blob URLs to prevent Next.js Image re-fetch on re-renders