46 KiB
Database Schema Documentation
This document provides a comprehensive analysis of the Tour Builder Platform database schema, including all models, fields, relationships, constraints, and configurations.
Overview
- Database: PostgreSQL
- ORM: Sequelize v6
- Table Naming:
freezeTableName: true- table names match model names exactly - Soft Delete: All models use
paranoid: true- records are soft-deleted viadeletedAttimestamp - Timestamps: All models include
createdAt,updatedAt, anddeletedAtcolumns - Primary Keys: All models use UUID v4 as primary key
Database Configuration
Configuration is environment-based in backend/src/db/db-config.ts:
| Environment | Database | Logging |
|---|---|---|
| production | DB_* env vars |
Disabled |
| development | db_tour_builder_platform |
Console |
| dev_stage | DB_* env vars |
Console |
Migration Settings:
- Migration storage:
sequelize(SequelizeMeta table) - Seeder storage:
sequelize
Entity-Relationship Overview
┌──────────────┐ ┌─────────────────┐ ┌──────────────────┐
│ users │────<│project_memberships│>───│ projects │
└──────────────┘ └─────────────────┘ └──────────────────┘
│ │
│ app_roleId │ hasMany
▼ ▼
┌──────────────┐ ┌──────────────────┐
│ roles │ │ tour_pages │
└──────────────┘ │ (ui_schema_json) │
│ └──────────────────┘
│ belongsToMany
▼
┌──────────────┐
│ permissions │
└──────────────┘
┌──────────────┐
│ assets │────< asset_variants
└──────────────┘
┌────────────────────────┐ snapshotted from ┌────────────────────────┐
│ project_element_defaults│ ─────────────────────── │ element_type_defaults │
└────────────────────────┘ (source_element_id) └────────────────────────┘
│ │
│ belongsTo │ (global defaults)
▼ │
┌──────────────────┐ │
│ projects │<────────────────────────────────────────┘
└──────────────────┘ (templates for new projects)
Note: Page elements, navigation links, and transition videos are stored directly in tour_pages.ui_schema_json rather than in separate tables. This simplifies the data model and avoids ID remapping issues when publishing between environments.
Core Models
1. users
User accounts for authentication and authorization.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
firstName |
TEXT | nullable | User's first name |
lastName |
TEXT | nullable | User's last name |
phoneNumber |
TEXT | nullable | Contact phone |
email |
TEXT | NOT NULL, UNIQUE | Email (validated) |
disabled |
BOOLEAN | NOT NULL, default: false | Account disabled flag |
password |
TEXT | NOT NULL | Bcrypt hashed password |
emailVerified |
BOOLEAN | NOT NULL, default: false | Email verification status |
emailVerificationToken |
TEXT | nullable | Token for email verification |
emailVerificationTokenExpiresAt |
DATE | nullable | Token expiry |
passwordResetToken |
TEXT | nullable | Password reset token |
passwordResetTokenExpiresAt |
DATE | nullable | Reset token expiry |
provider |
TEXT | NOT NULL, default: 'local' | Auth provider (local, google, microsoft) |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication key |
app_roleId |
UUID | FK → roles.id | User's application role |
createdById |
UUID | FK → users.id | Record creator |
updatedById |
UUID | FK → users.id | Last modifier |
Indexes:
email(unique)app_roleIddeletedAt
Associations:
belongsToroles (asapp_role)hasManyproject_membershipshasManypresigned_url_requestshasManypublish_eventshasManyaccess_logsbelongsToManypermissions (ascustom_permissions)hasManyfile (asavatar, polymorphic)
Hooks:
beforeCreate: Trims string fields, auto-generates password for OAuth users, setsemailVerified: truefor OAuthbeforeUpdate: Trims string fields
2. roles
Application-level roles for RBAC.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
name |
TEXT | NOT NULL, len: 1-100 | Role name |
role_customization |
TEXT | nullable | Custom role settings |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Associations:
hasManyusers (asusers_app_role)belongsToManypermissions (throughrolesPermissionsPermissions)
Seeded Roles:
- Administrator (full access)
- Platform Owner
- Account Manager
- Tour Designer
- Content Reviewer
- Analytics Viewer
- Public (minimal access)
3. permissions
Individual permission definitions for RBAC.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
name |
TEXT | NOT NULL, UNIQUE, len: 1-100 | Permission name |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Permission Naming Convention:
CREATE_<ENTITY>- Create recordsREAD_<ENTITY>- Read recordsUPDATE_<ENTITY>- Modify recordsDELETE_<ENTITY>- Delete recordsREAD_API_DOCS- Access API documentationCREATE_SEARCH- Perform searches
Seeded Entities with CRUD Permissions: users, roles, permissions, projects, project_memberships, assets, asset_variants, presigned_url_requests, tour_pages, project_audio_tracks, publish_events, pwa_caches, access_logs, element_type_defaults, project_element_defaults
Private production presentation access is intentionally not part of the generic
CRUD permission seed set. It is represented by project visibility plus explicit
customer grants in production_presentation_access.
4. projects
Virtual tour projects - the main organizational unit.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
name |
TEXT | NOT NULL, len: 1-255 | Project name |
slug |
TEXT | NOT NULL, UNIQUE, regex: ^[a-z0-9_-]+$/i, len: 1-255 |
URL-safe identifier |
description |
TEXT | nullable | Project description |
logo_url |
TEXT | nullable | Project logo URL |
favicon_url |
TEXT | nullable | Favicon URL |
og_image_url |
TEXT | nullable | Open Graph image URL |
production_presentation_visibility |
ENUM | NOT NULL, default: 'public' | Production runtime visibility: public, private |
design_width |
INTEGER | nullable, default: 1920 | Design canvas width (px) |
design_height |
INTEGER | nullable, default: 1080 | Design canvas height (px) |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
slug(unique)deletedAt
Associations:
hasManyproject_membershipshasManyassetshasManypresigned_url_requestshasManytour_pageshasManyproject_audio_trackshasManyproject_element_defaultshasManypublish_eventshasManypwa_cacheshasManyaccess_logshasManyproduction_presentation_accesshasManyproject_ui_control_settings
Cascade Behavior: All child records are deleted when project is deleted.
Auto-Snapshot on Create: When a project is created, all element_type_defaults records are automatically snapshotted to project_element_defaults for the new project.
Global UI Controls
Global UI controls configure the system-owned fullscreen, sound, and offline
buttons. Defaults live in global_ui_control_defaults, project/environment
overrides live in project_ui_control_settings, and page overrides live in
tour_pages.global_ui_controls_settings_json.
Sizes are stored as canvas-width-relative percentage fields
(buttonSizePercent, iconSizePercent, borderRadiusPercent). Positions are
stored as canvas-relative percentages (xPercent, yPercent) for stable
placement across devices.
Icons, background colors, and border colors are state-specific:
defaultIconUrl/activeIconUrl, defaultBackgroundColor/activeBackgroundColor,
and defaultBorderColor/activeBorderColor.
global_ui_control_defaults
Singleton table for platform-wide defaults.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default UUIDv4 | Primary identifier |
settings_json |
JSON | NOT NULL | Defaults for offline, fullscreen, sound |
createdById |
UUID | nullable FK users | Creator |
updatedById |
UUID | nullable FK users | Last updater |
createdAt, updatedAt, deletedAt |
DATE | paranoid timestamps | Lifecycle fields |
project_ui_control_settings
Project/environment-level overrides.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default UUIDv4 | Primary identifier |
projectId |
UUID | NOT NULL, FK projects, cascade delete | Owning project |
environment |
ENUM | NOT NULL: dev, stage, production |
Content environment |
source_key |
TEXT | nullable | Snapshot/publish/clone provenance |
settings_json |
JSON | NOT NULL | Project-level UI-control overrides |
importHash |
STRING(255) | nullable unique | Import deduplication |
createdById, updatedById |
UUID | nullable FK users | Audit users |
createdAt, updatedAt, deletedAt |
DATE | paranoid timestamps | Lifecycle fields |
Indexes:
- unique partial index on
("projectId", environment)wheredeletedAt IS NULL - index on
deletedAt
5. production_presentation_access
Explicit customer access grants for private production presentations.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
projectId |
UUID | FK → projects.id, NOT NULL | Private production presentation project |
userId |
UUID | FK → users.id, NOT NULL | Customer user allowed to view the presentation |
createdById |
UUID | FK → users.id | Record creator |
updatedById |
UUID | FK → users.id | Last modifier |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
projectIduserIdprojectId, userId(unique for active rows wheredeletedAt IS NULL)
Access Rules:
- Public production projects do not require rows in this table.
- Staff users with any RBAC permission can view every private production presentation.
- Public-role customer users with no RBAC permissions need an active row for each private production presentation.
6. project_memberships
Junction table linking users to projects with access levels.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
access_level |
ENUM | NOT NULL, default: 'viewer' | Access level: owner, editor, reviewer, viewer |
is_active |
BOOLEAN | NOT NULL, default: false | Membership active status |
invited_at |
DATE | nullable | Invitation timestamp |
accepted_at |
DATE | nullable | Acceptance timestamp |
projectId |
UUID | FK → projects.id | Associated project |
userId |
UUID | FK → users.id | Associated user |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
projectIduserIdprojectId, userId(unique composite)is_activedeletedAt
Cascade Behavior: Deleted when associated project or user is deleted.
Tour Content Models
7. tour_pages
Individual pages/scenes within a tour.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
environment |
ENUM | NOT NULL, default: 'dev' | Environment: dev, stage, production |
source_key |
TEXT | nullable | Reference to source version when published |
name |
TEXT | NOT NULL, len: 1-255 | Page display name |
slug |
TEXT | NOT NULL, regex: ^[a-z0-9_-]+$/i, len: 1-255 |
URL-safe identifier |
sort_order |
INTEGER | NOT NULL, default: 0 | Presentation display order; first sorted page is the entry page |
background_image_url |
TEXT | nullable | Background image URL |
background_video_url |
TEXT | nullable | Background video URL |
background_embed_url |
TEXT | nullable | Background 360/embed URL |
background_audio_url |
TEXT | nullable | Background audio URL |
background_loop |
BOOLEAN | NOT NULL, default: false | Loop background media |
background_video_autoplay |
BOOLEAN | NOT NULL, default: true | Autoplay background video |
background_video_loop |
BOOLEAN | NOT NULL, default: true | Loop background video |
background_video_muted |
BOOLEAN | NOT NULL, default: true | Mute background video |
background_video_start_time |
DECIMAL(10,1) | nullable | Background video start time (seconds) |
background_video_end_time |
DECIMAL(10,1) | nullable | Background video end time (seconds) |
background_video_play_once |
BOOLEAN | NOT NULL, default: false | Play video only once per session (show last frame on revisit) |
background_audio_autoplay |
BOOLEAN | NOT NULL, default: true | Autoplay background audio |
background_audio_loop |
BOOLEAN | NOT NULL, default: true | Loop background audio |
background_audio_start_time |
DECIMAL(10,1) | nullable | Background audio start time (seconds) |
background_audio_end_time |
DECIMAL(10,1) | nullable | Background audio end time (seconds) |
design_width |
INTEGER | nullable, default: null | Design canvas width (px) - copied from project on save |
design_height |
INTEGER | nullable, default: null | Design canvas height (px) - copied from project on save |
requires_auth |
BOOLEAN | NOT NULL, default: false | Requires authentication |
ui_schema_json |
JSON | nullable | UI element schema |
projectId |
UUID | FK → projects.id | Parent project |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
projectIdprojectId, environment, slug(unique composite)projectId, environment, sort_orderdeletedAt
Ordering semantics:
- Constructor page reordering updates
sort_orderfor dev pages only. - Constructor page duplication creates a new dev row with a
unique slug, fresh page ID, fresh inline element IDs in
ui_schema_json, andsort_order = max(project dev sort_order) + 1. - Constructor page deletion removes the dev row through the standard
DELETE /api/tour_pages/:idendpoint; stage and production keep their previous copies until Save to Stage and Publish run. - Runtime loaders sort by
sort_order; the first page after sorting is the presentation entry page. - Save to Stage copies dev
sort_orderto stage. Publish copies stagesort_orderto production.
Associations:
belongsToprojects (asproject)
UI Schema JSON Structure:
The ui_schema_json field contains all page elements and navigation configuration:
{
"elements": [{
"id": "unique-element-id",
"type": "navigation_next",
"name": "Next Page Button",
"xPercent": 90,
"yPercent": 85,
"widthPercent": 8,
"heightPercent": 10,
"targetPageSlug": "page-2",
"transitionVideoUrl": "assets/.../transition.mp4",
"iconUrl": "assets/.../icon.png",
"styleJson": { ... }
}]
}
Element Types:
navigation_next- Forward navigation buttonnavigation_prev- Back navigation buttonspot- Hotspot/clickable areadescription- Text descriptiontooltip- Hover tooltipgallery- Image gallerycarousel- Image carousellogo- Logo elementvideo_player- Video playeraudio_player- Audio playerpopup- Popup/modal
Asset Management Models
7. assets
Media files (images, videos, audio, documents) used in tours.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
name |
TEXT | nullable, len: 0-255 | Asset display name |
asset_type |
ENUM | NOT NULL | Media type: image, video, audio, file |
type |
ENUM | NOT NULL, default: 'general' | Usage type (see values below) |
cdn_url |
TEXT | nullable | Public CDN URL |
storage_key |
TEXT | nullable | S3/storage key |
mime_type |
TEXT | nullable, validated | MIME type (e.g., image/png) |
size_mb |
DECIMAL | nullable | File size in MB |
width_px |
INTEGER | nullable | Width in pixels |
height_px |
INTEGER | nullable | Height in pixels |
duration_sec |
DECIMAL | nullable | Duration for audio/video |
frame_rate |
DECIMAL | nullable | Video FPS from backend ffprobe |
checksum |
TEXT | nullable | File checksum |
is_public |
BOOLEAN | NOT NULL, default: false | Publicly accessible |
projectId |
UUID | FK → projects.id | Parent project |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Asset Usage Types:
icon- UI iconsbackground_image- Page backgroundsaudio- Audio filesvideo- Video filestransition- Transition videoslogo- Project logosfavicon- Favicon imagesdocument- PDF/documentsgeneral- General assets
Indexes:
projectIdstorage_keypartial active-row index (assets_storage_key_active, wheredeletedAt IS NULLandstorage_key IS NOT NULL) for transition/reversed-video lookup by canonical storage keyasset_typetypeis_publicdeletedAt
Associations:
belongsToprojects (asproject)hasManyasset_variants (asasset_variants_asset)
8. asset_variants
Processed variants of assets (thumbnails, transcoded videos, reversed videos, etc.).
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
variant_type |
ENUM | nullable | Variant type (see values below) |
cdn_url |
TEXT | nullable, len: 0-2048, URL validated | Variant CDN URL |
storage_key |
TEXT | nullable | Private storage path (e.g., assets/{assetId}/reversed.mp4) |
width_px |
INTEGER | nullable, min: 0 | Width in pixels |
height_px |
INTEGER | nullable, min: 0 | Height in pixels |
size_mb |
DECIMAL | nullable, min: 0 | File size in MB |
assetId |
UUID | FK → assets.id | Parent asset |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Variant Types:
thumbnail- Small previewpreview- Medium previewwebp- WebP formatmp4_low- Low-quality MP4mp4_high- High-quality MP4original- Original filereversed- Reversed video for back navigation transitions (generated server-side with FFmpeg)
Cascade Behavior: Deleted when parent asset is deleted.
Indexes:
assetId, variant_typepartial active-row index (asset_variants_asset_id_variant_type_active, wheredeletedAt IS NULL) for asset variant joins and reversed-variant lookup
9. presigned_url_requests
Tracks presigned URL requests for secure uploads/downloads.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
purpose |
ENUM | nullable | Purpose: upload, download |
asset_type |
ENUM | nullable | Asset type: image, video, audio, file |
requested_key |
TEXT | nullable, len: 0-1024 | Requested storage key |
mime_type |
TEXT | nullable, len: 0-255, validated | Expected MIME type |
requested_size_mb |
DECIMAL | nullable, min: 0 | Expected file size |
expires_at |
DATE | nullable | URL expiration time |
status |
TEXT | nullable | Request status |
projectId |
UUID | FK → projects.id | Associated project |
userId |
UUID | FK → users.id | Requesting user |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Audio & Media Models
10. project_audio_tracks
Background audio tracks for projects.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
environment |
ENUM | NOT NULL, default: 'dev' | Environment: dev, stage, production |
source_key |
TEXT | nullable | Reference to source version when published |
name |
TEXT | nullable, len: 0-255 | Track name |
slug |
TEXT | nullable | URL-safe identifier |
url |
TEXT | nullable | Audio file URL |
loop |
BOOLEAN | NOT NULL, default: false | Loop playback |
volume |
DECIMAL | nullable, min: 0, max: 1 | Volume level (0.0-1.0) |
sort_order |
INTEGER | nullable | Playback order |
is_enabled |
BOOLEAN | NOT NULL, default: false | Track enabled |
projectId |
UUID | FK → projects.id | Parent project |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Note: The environment field is NOT NULL with default 'dev' for consistency with tour_pages.
11. project_transition_settings
Environment-aware project-level transition settings for CSS-based page transitions. Settings cascade: Element → Project → Global → Hardcoded defaults.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
environment |
ENUM | NOT NULL | Environment: dev, stage, production |
source_key |
TEXT | nullable | Reference to source record when published |
transition_type |
TEXT | NOT NULL, default: 'fade' | CSS transition type (fade, none) |
duration_ms |
INTEGER | NOT NULL, default: 700 | Transition duration in milliseconds |
easing |
TEXT | NOT NULL, default: 'ease-in-out' | CSS easing function |
overlay_color |
TEXT | NOT NULL, default: '#000000' | Transition overlay color |
projectId |
UUID | FK → projects.id, NOT NULL | Parent project |
createdById |
UUID | FK → users.id, nullable | Creator user |
updatedById |
UUID | FK → users.id, nullable | Last updater |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
project_transition_settings_project_env_unique- UNIQUE on (projectId, environment) WHERE deletedAt IS NULL
Associations:
belongsToprojects (asproject) - CASCADE on deletebelongsTousers (ascreatedBy,updatedBy)
Publishing Integration: Copied between environments during Save to Stage (dev → stage) and Publish (stage → production). The source_key tracks lineage.
Cascade Resolution: When determining transition settings:
- Element-level settings (from
ui_schema_json) - Project-level settings (this table, environment-specific)
- Global defaults (
global_transition_defaults) - Hardcoded fallback (fade, 700ms, ease-in-out, #000000)
Publishing & Caching Models
12. publish_events
Records of content publishing between environments.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
title |
STRING | nullable, len: 0-255 | Event title |
description |
TEXT | nullable, len: 0-5000 | Event description |
from_environment |
ENUM | NOT NULL | Source: dev, stage, production |
to_environment |
ENUM | NOT NULL | Target: dev, stage, production |
started_at |
DATE | nullable | Start timestamp |
finished_at |
DATE | nullable | Completion timestamp |
status |
ENUM | NOT NULL, default: 'queued' | Status: queued, running, success, failed |
error_message |
TEXT | nullable | Error details |
pages_copied |
INTEGER | nullable, min: 0 | Pages copied count |
audios_copied |
INTEGER | nullable, min: 0 | Audio tracks copied count |
projectId |
UUID | FK → projects.id | Published project |
userId |
UUID | FK → users.id | Publishing user |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
projectIduserIdstatusstarted_at
Cascade Behavior: userId set to NULL when user is deleted (preserves audit trail).
13. pwa_caches
PWA cache configurations for offline support.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
environment |
ENUM | nullable | Environment: dev, stage, production |
cache_version |
TEXT | nullable, len: 0-255 | Cache version string |
manifest_json |
JSON | nullable | PWA manifest configuration |
asset_list_json |
JSON | nullable | List of cached assets |
generated_at |
DATE | nullable | Generation timestamp |
is_active |
BOOLEAN | NOT NULL, default: false | Cache active status |
projectId |
UUID | FK → projects.id | Parent project |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Audit & Logging Models
14. access_logs
Audit trail for tour access.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
environment |
ENUM | NOT NULL | Access context: admin, stage, production |
path |
TEXT | nullable, len: 0-2048 | Accessed path |
ip_address |
TEXT | nullable, len: 0-45 | Client IP (IPv4/IPv6) |
user_agent |
TEXT | nullable, len: 0-1024 | Browser user agent |
accessed_at |
DATE | NOT NULL, default: NOW | Access timestamp |
projectId |
UUID | FK → projects.id | Accessed project |
userId |
UUID | FK → users.id | Accessing user (if authenticated) |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
projectIdenvironmentuserIdaccessed_at
Cascade Behavior: userId set to NULL when user is deleted (preserves audit trail).
Element Default Settings Models
These models implement a two-tier settings hierarchy for UI elements:
- Global defaults (
element_type_defaults) - Platform-wide default settings per element type - Project defaults (
project_element_defaults) - Project-specific overrides, snapshotted from global on project creation
Instance element settings are stored directly in tour_pages.ui_schema_json as part of each element's configuration.
15. element_type_defaults
Global platform-wide default settings for each element type. These serve as templates that are snapshotted to new projects.
Note: This table was renamed from ui_elements to element_type_defaults for clarity.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
element_type |
TEXT | NOT NULL, UNIQUE, len: 1-100 | Element type identifier |
name |
TEXT | NOT NULL, len: 1-255 | Display name |
sort_order |
INTEGER | NOT NULL, default: 0 | Display order in UI |
is_active |
VIRTUAL | getter: true | Virtual active field |
settings_json |
TEXT | nullable | Default settings JSON |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Field Aliasing: The model exposes default_settings_json as a property name, but it maps to the settings_json column in the database via field: 'settings_json'. This provides a clearer API name while maintaining backward compatibility with the database schema.
Virtual Field: is_active is a VIRTUAL field that always returns true (computed, not stored in database).
Indexes:
element_type(unique)sort_orderdeletedAt
Associations:
hasManyproject_element_defaults (asproject_defaults)
Auto-Initialization: The API includes an ensureInitialized() method that automatically seeds default records if the table is empty. This runs before any CRUD operation.
Seeded Element Types (11 types auto-seeded):
navigation_next- Forward navigation button (sort_order: 1)navigation_prev- Back navigation button (sort_order: 2)tooltip- Hover tooltip (sort_order: 3)description- Text description (sort_order: 4)gallery- Image gallery (sort_order: 5)carousel- Image carousel (sort_order: 6)video_player- Video player (sort_order: 7)audio_player- Audio player (sort_order: 8)spot- Hotspot/clickable area (sort_order: 9)logo- Logo element (sort_order: 10)popup- Popup/modal (sort_order: 11)
16. project_element_defaults
Project-specific element default settings. Created automatically when a project is created by snapshotting all global element_type_defaults. Can be customized per-project without affecting global defaults or other projects.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
element_type |
TEXT | NOT NULL, len: 1-100 | Element type identifier |
name |
TEXT | nullable, len: 0-255 | Custom display name |
sort_order |
INTEGER | NOT NULL, default: 0 | Display order in UI |
settings_json |
TEXT | nullable | Project-specific settings JSON |
source_element_id |
UUID | FK → element_type_defaults.id, nullable | Reference to global default (for reset/diff) |
snapshot_version |
INTEGER | NOT NULL, default: 1 | Version counter (incremented on reset) |
projectId |
UUID | FK → projects.id, NOT NULL | Parent project |
importHash |
STRING(255) | UNIQUE, nullable | Import deduplication |
Indexes:
projectIdprojectId, element_type(unique composite)element_typesource_element_iddeletedAt
Associations:
belongsToprojects (asproject) - CASCADE on deletebelongsToelement_type_defaults (assource_element) - SET NULL on delete
Custom findAll with Project Filtering:
The API implements a custom findAll() method that supports filtering by project:
- Query params:
?projectId=<uuid>or?project=<uuid|name> - Supports multiple values:
?projectId=uuid1|uuid2 - Can filter by project UUID or project name (case-insensitive)
- Includes related
projectandsource_elementassociations - Default ordering:
sort_order ASC
Auto-Snapshot Behavior:
When a new project is created, all records from element_type_defaults are automatically copied to project_element_defaults for that project. This ensures each project starts with consistent defaults while allowing customization.
Reset Functionality:
Projects can reset their element defaults back to the current global defaults via the /api/project-element-defaults/:id/reset endpoint. This increments snapshot_version and copies current global settings.
Diff Functionality:
The /api/project-element-defaults/:id/diff endpoint compares project defaults with global defaults to show customizations.
16. file (table: files)
Polymorphic file attachments (e.g., user avatars).
Note: This model does NOT have freezeTableName: true, so the table name is pluralized to files.
| Field | Type | Constraints | Description |
|---|---|---|---|
id |
UUID | PK, default: UUIDv4 | Primary identifier |
belongsTo |
STRING(255) | nullable | Parent table name |
belongsToId |
UUID | nullable | Parent record ID |
belongsToColumn |
STRING(255) | nullable | Column name |
name |
STRING(2083) | NOT NULL | File name |
sizeInBytes |
INTEGER | nullable | File size |
privateUrl |
STRING(2083) | nullable | Private storage URL |
publicUrl |
STRING(2083) | NOT NULL | Public access URL |
Polymorphic Usage:
Files are attached to records via belongsTo/belongsToId/belongsToColumn columns.
Example: User avatar has belongsTo: 'users', belongsToId: <user_id>, belongsToColumn: 'avatar'
Junction Tables
rolesPermissionsPermissions
Many-to-many relationship between roles and permissions.
| Field | Type | Constraints |
|---|---|---|
roles_permissionsId |
UUID | PK, FK → roles.id |
permissionId |
UUID | PK, FK → permissions.id |
createdAt |
TIMESTAMP WITH TIME ZONE | NOT NULL |
updatedAt |
TIMESTAMP WITH TIME ZONE | NOT NULL |
Indexes:
permissionId
usersCustom_permissionsPermissions
Many-to-many relationship for user custom permissions.
| Field | Type | Constraints |
|---|---|---|
users_custom_permissionsId |
UUID | FK → users.id |
permissionId |
UUID | FK → permissions.id |
Database Indexes Summary
| Table | Index | Fields | Type |
|---|---|---|---|
| users | unique | ||
| users | app_roleId | app_roleId | - |
| users | deletedAt | deletedAt | - |
| projects | slug | slug | unique |
| projects | deletedAt | deletedAt | - |
| production_presentation_access | projectId | projectId | - |
| production_presentation_access | userId | userId | - |
| production_presentation_access | composite | projectId, userId | unique active rows |
| project_memberships | composite | projectId, userId | unique |
| tour_pages | composite | projectId, environment, slug | unique |
| tour_pages | sort | projectId, environment, sort_order | - |
| assets | projectId | projectId | - |
| assets | asset_type | asset_type | - |
| assets | type | type | - |
| assets | is_public | is_public | - |
| element_type_defaults | element_type | element_type | unique |
| element_type_defaults | sort_order | sort_order | - |
| element_type_defaults | deletedAt | deletedAt | - |
| project_element_defaults | projectId | projectId | - |
| project_element_defaults | composite | projectId, element_type | unique |
| project_element_defaults | element_type | element_type | - |
| project_element_defaults | source_element_id | source_element_id | - |
| project_element_defaults | deletedAt | deletedAt | - |
| publish_events | status | status | - |
| publish_events | started_at | started_at | - |
| access_logs | accessed_at | accessed_at | - |
Foreign Key Constraints
All foreign key constraints are enforced at the database level via migration 20260319000001-add-foreign-key-constraints.js.
| Child Table | Column | Parent Table | On Delete | On Update |
|---|---|---|---|---|
| asset_variants | assetId | assets | CASCADE | CASCADE |
| assets | projectId | projects | CASCADE | CASCADE |
| tour_pages | projectId | projects | CASCADE | CASCADE |
| project_memberships | projectId | projects | CASCADE | CASCADE |
| project_memberships | userId | users | CASCADE | CASCADE |
| production_presentation_access | projectId | projects | CASCADE | CASCADE |
| production_presentation_access | userId | users | CASCADE | CASCADE |
| production_presentation_access | createdById | users | SET NULL | CASCADE |
| production_presentation_access | updatedById | users | SET NULL | CASCADE |
| presigned_url_requests | projectId | projects | CASCADE | CASCADE |
| presigned_url_requests | userId | users | CASCADE | CASCADE |
| project_audio_tracks | projectId | projects | CASCADE | CASCADE |
| project_element_defaults | projectId | projects | CASCADE | CASCADE |
| project_element_defaults | source_element_id | element_type_defaults | SET NULL | CASCADE |
| publish_events | projectId | projects | CASCADE | CASCADE |
| publish_events | userId | users | SET NULL | CASCADE |
| pwa_caches | projectId | projects | CASCADE | CASCADE |
| access_logs | projectId | projects | CASCADE | CASCADE |
| access_logs | userId | users | SET NULL | CASCADE |
| users | app_roleId | roles | SET NULL | CASCADE |
Migration History
| Migration | Description |
|---|---|
20260319000001-add-foreign-key-constraints.js |
Adds all FK constraints to enforce referential integrity |
20260319000002-remove-redundant-deletion-columns.js |
Removes deprecated is_deleted and deleted_at_time columns from assets and projects |
20260326000001-rename-ui-elements-to-element-type-defaults.js |
Renames ui_elements table to element_type_defaults for clarity |
20260326000002-convert-element-type-enum-to-text.js |
Converts element type from ENUM to TEXT for flexibility |
20260326000003-create-project-element-defaults.js |
Creates project_element_defaults table for project-specific element settings |
20260326000004-backfill-project-element-defaults.js |
Backfills project_element_defaults for existing projects by snapshotting global defaults |
20260326000005-fix-project-audio-tracks-environment.js |
Fixes project_audio_tracks.environment to NOT NULL with default 'dev' |
20260326000006-copy-dev-to-stage.js |
Copies existing dev content to stage environment for all projects (initializes dev→stage workflow) |
20260326043002-enforce-environment-not-null.js |
Enforces NOT NULL constraint on environment columns in tour_pages and transitions |
20260326050442-remove-project-phase-column.js |
Removes redundant phase column from projects table (environment is on tour_pages) |
20260326054410-remove-entry-page-slug-column.js |
Removes entry_page_slug from projects (entry page is first by sort_order) |
20260326060000-convert-targetpageid-to-slug.js |
Converts targetPageId to targetPageSlug in ui_schema_json for environment-safe navigation |
20260326060001-drop-page-elements-table.js |
Drops unused page_elements table (data stored in ui_schema_json) |
20260326060002-drop-page-links-table.js |
Drops unused page_links table (navigation stored in ui_schema_json) |
20260326060003-drop-transitions-table.js |
Drops unused transitions table (transitionVideoUrl stored in ui_schema_json) |
20260326171017-add-missing-element-type-defaults.js |
Adds missing element types (spot, logo, popup) to element_type_defaults and backfills project_element_defaults |
20260327000001-sync-all-element-type-defaults.js |
Syncs all 11 element types with correct sort_order and backfills missing project_element_defaults for all projects |
20260331024423-remove-unused-theme-columns-from-projects.js |
Removes unused theme_config_json, custom_css_json, cdn_base_url columns from projects table |
20260331054340-remove-duplicate-element-type-defaults.js |
Removes duplicate element_type_defaults records created during earlier migrations |
20260331063424-cleanup-invalid-element-type-defaults.js |
Cleans up invalid element_type_defaults entries and ensures data integrity |
20260403000001-add-background-video-settings.js |
Adds background video playback settings to tour_pages (autoplay, loop, muted, start_time, end_time) |
20260409000001-add-design-dimensions-to-projects.js |
Adds design_width and design_height columns to projects table for canvas scaling |
20260409111309-add-design-dimensions-to-tour-pages.js |
Adds design_width and design_height columns to tour_pages table for presentation isolation |
20260422000001-add-background-video-play-once.js |
Adds background_video_play_once column to tour_pages for session-scoped single playback |
20260605000001-add-background-audio-settings.js |
Adds background audio playback settings to tour_pages (autoplay, loop, start_time, end_time) |
20260613000001-add-background-embed-url-to-tour-pages.js |
Adds background_embed_url to tour_pages for 360/embed page backgrounds |
20260626000001-add-private-production-presentation-access.js |
Adds project production visibility and customer access grants for private production presentations |
20260626000002-grant-account-manager-create-users.js |
Grants CREATE_USERS to Account Manager for customer viewer creation |
Seeders
| Seeder | Description |
|---|---|
20200430130759-admin-user.js |
Creates initial admin and test users |
20200430130760-user-roles.js |
Creates roles, permissions, and role-permission assignments |
20231127130745-sample-data.js |
Creates sample projects, pages, assets, and other demo data |
Initial Users:
- Admin:
admin@flatlogic.com(admin password from config) - John Doe:
john@doe.com(user password from config) - Client:
client@hello.com(user password from config)
Base DB API Patterns
All entity DB APIs extend GenericDBApi which provides:
Configurable Properties
MODEL- Sequelize model reference (required, must be defined in subclass)TABLE_NAME- Derived from MODEL.getTableName()SEARCHABLE_FIELDS- Text search fields (ILIKE)RANGE_FIELDS- Date/number range filtersENUM_FIELDS- Exact match filtersRELATION_FILTERS- Related entity filtersCSV_FIELDS- Export field list (default:['id', 'createdAt'])AUTOCOMPLETE_FIELD- Field for autocomplete (default: 'name')ASSOCIATIONS- Many-to-many relationshipsFIND_BY_INCLUDES- Eager loading for findByFIND_ALL_INCLUDES- Eager loading for findAllgetFieldMapping(data)- Transform input data before save (default: returns data unchanged)
Standard Methods
create(data, options)- Create record with associationsbulkImport(data, options)- Bulk create recordsupdate({ id, data, currentUser, transaction, runtimeContext })- Update recorddeleteByIds({ ids, currentUser, transaction, runtimeContext })- Soft delete multiple recordsremove({ id, currentUser, transaction, runtimeContext })- Soft delete single recordfindBy(where, options)- Find single recordfindAll(filter, options)- Paginated list with filtersfindAllAutocomplete({ query, limit, offset }, options)- Autocomplete searchtoCSV(rows)- Export to CSV
Environment-Based Content
The platform uses an environment-based content model for publishing workflow:
- dev - Development environment (editable in constructor)
- stage - Staging/preview environment (for review before production)
- production - Live production environment (public-facing)
Content Flow
┌─────────┐ Save to Stage ┌─────────┐ Publish ┌────────────┐
│ dev │ ─────────────────── │ stage │ ───────────── │ production │
└─────────┘ └─────────┘ └────────────┘
▲
│
Constructor
edits
Tables with Environment Column
tour_pages- Pages withenvironmentcolumnproject_audio_tracks- Audio tracks withenvironmentcolumnproject_transition_settings- CSS transition settings withenvironmentcolumn
Source Key Tracking
When content is copied between environments, the source_key field stores the ID of the source record. This enables:
- Tracking content lineage
- Identifying which stage records came from dev
- Rolling back changes if needed
Environment Access Control
Access to content is controlled by environment with strict isolation:
| Environment | Authentication | Access | Use Case |
|---|---|---|---|
| dev | Required (JWT) | Admin/Constructor only | Editing in constructor |
| stage | Required (JWT) | Authenticated users | Review workspace before publish |
| production | Public (no auth) | Anyone | Published public tours |
Security Layers:
-
Backend Middleware (
requireRuntimeReadOrAuth): Onlyproductionenvironment allows unauthenticated GET requests. Stage and dev require JWT authentication. -
Runtime Context API (
getRuntimeEnvironment): Blocksdevenvironment from being accessed viaX-Runtime-Environmentheader. Onlyproductionandstageare allowed. -
Database Filtering (
applyRuntimeEnvironment): Applies environment filter to all queries when runtime context is present. -
Frontend Routes:
/p/[slug]→ production (LayoutGuest)/p/[slug]/stage→ stage (LayoutAuthenticated)/constructor→ dev (LayoutAuthenticated)
Navigation uses targetPageSlug (not targetPageId) in ui_schema_json to ensure navigation works correctly across environments since page IDs differ between dev, stage, and production.
Data Types Reference
| Sequelize Type | PostgreSQL Type | Usage |
|---|---|---|
| UUID | uuid | Primary keys, foreign keys |
| TEXT | text | Long strings (unlimited) |
| STRING(n) | varchar(n) | Limited strings |
| INTEGER | integer | Whole numbers |
| DECIMAL | numeric | Precise decimals |
| BOOLEAN | boolean | True/false |
| DATE | timestamp with time zone | Dates and times |
| JSON | jsonb | Structured data |
| ENUM | enum type | Fixed value sets |
| VIRTUAL | (none) | Computed fields |
Common Model Options
Most models share these Sequelize options:
{
timestamps: true, // Adds createdAt, updatedAt
paranoid: true, // Soft delete via deletedAt
freezeTableName: true, // Table name = model name
}
Exception: The file model does not set freezeTableName: true, so its table name is files (pluralized by Sequelize default).
All records include audit fields:
createdAt- Creation timestampupdatedAt- Last modification timestampdeletedAt- Soft deletion timestamp (null if not deleted)createdById- Creating user's IDupdatedById- Last modifying user's ID