Files
pulse-signage/docs/technical/schema.md
T

17 KiB

Database Schema

This app creates and maintains its schema at startup through src/db/index.js. The sections below summarize the current tables and their purpose.

Tables generally use a numeric auto-increment id primary key. The relationship table d_announcement_screens intentionally uses the composite (announcement_id, screen_id) primary key instead. Natural and relationship keys remain as unique constraints where needed. Timestamps are stored as created_at and modified_at when a table supports auditing.

Admin

  • a_users - admin user profiles and app-specific account controls.
  • a_roles - named role definitions.
  • a_permissions - permission catalog seeded from the application constants.
  • a_role_permissions - many-to-many mapping between roles and permissions.
  • a_user_roles - many-to-many mapping between users and roles.
  • a_sessions - Better Auth session tokens with application View-as metadata.
  • a_accounts - Better Auth credentials and linked identity-provider accounts.
  • a_verifications - Better Auth email OTP verification and reset challenges.
  • a_user_two_factors - Better Auth authenticator secrets, backup codes, and per-user verification lock state.
  • a_user_passkeys - Better Auth passkey credentials and authenticator metadata.
  • a_login_attempts - Better Auth database-backed rate-limit counters.

a_users

  • id, name, username, email, email_verified, image, pending_email, must_change_password, account_locked, created_at, created_by, modified_at, modified_by
  • username is unique.
  • email is Better Auth's unique, non-null email identity; synthetic addresses are used when the app email is absent.

a_accounts

  • id, account_id, provider_id, user_id, access_token, refresh_token, id_token, access_token_expires_at, refresh_token_expires_at, scope, password, created_at, updated_at
  • (provider_id, account_id) is unique.
  • Credential passwords are stored in password on the credential provider account using Better Auth's native scrypt format; legacy PBKDF2 hashes upgrade after successful sign-in. Password hashes are not stored on a_users.
  • Foreign key: - user_id -> a_users.id with ON DELETE CASCADE

a_verifications

  • id, identifier, value, expires_at, created_at, updated_at
  • Indexed by identifier; Better Auth stores one-time email codes and their expiry here.

a_user_two_factors

  • id, user_id, secret, backup_codes, verified, failed_verification_count, locked_until
  • user_id is unique; each user has at most one authenticator record.
  • Foreign key: user_id -> a_users.id with ON DELETE CASCADE.

a_user_passkeys

  • id, name, public_key, user_id, credential_id, counter, device_type, backed_up, transports, created_at, aaguid
  • credential_id is unique; user_id is indexed.
  • Foreign key: user_id -> a_users.id with ON DELETE CASCADE.

a_login_attempts

  • id, rate_key, failed_count, last_request
  • rate_key is unique.

a_roles

  • id, role_key, name, description, require_two_factor, created_at, created_by, modified_at, modified_by
  • role_key is unique.
  • name is unique.
  • require_two_factor defaults to false; users must enroll when any assigned role requires two-factor authentication.

a_permissions

  • id, permission_key, name, section_name, description, created_at, modified_at
  • permission_key is unique.

a_role_permissions

  • id, role_id, permission_id, created_at, modified_at
  • Foreign keys: - role_id -> a_roles.id - permission_id -> a_permissions.id
  • Unique key: (role_id, permission_id)

a_user_roles

  • id, user_id, role_id, created_at, modified_at
  • Foreign keys: - user_id -> a_users.id - role_id -> a_roles.id
  • Unique key: (user_id, role_id)

a_sessions

  • id, session_hash, user_id, ip_address, user_agent, expires_at, created_at, created_by, last_used_at, modified_by
  • session_hash is unique.
  • Foreign key: - user_id -> a_users.id

Content

  • c_canvas_sizes - reusable canvas presets for templates.
  • c_playlists - playlist definitions, playback options, and canvas assignment.
  • c_templates - slide templates with canvas and background settings.
  • c_template_regions - template region layout and metadata.
  • c_slides - slide records with template binding, JSON content, and thumbnail path.
  • c_media_assets - reusable uploaded media files referenced by slides and templates.
  • c_playlist_slides - ordered playlist items and timing.
  • c_playlist_slide_schedule_rules - rule rows attached to playlist slides.

c_canvas_sizes

  • id, name, width, height, created_at, created_by, modified_at, modified_by
  • (width, height) is unique.

c_playlists

  • id, name, fade_between_slides, skip_unavailable_rtmp, canvas_id, created_at, created_by, modified_at, modified_by
  • Foreign key: - canvas_id -> c_canvas_sizes.id with ON DELETE SET NULL

c_templates

  • id, name, canvas_size_id, background_image_path, background_color, background_gradient, created_at, created_by, modified_at, modified_by
  • background_gradient stores normalized linear gradient settings as JSON text, including angle and colour stops.
  • Foreign key: - canvas_size_id -> c_canvas_sizes.id with ON DELETE SET NULL

c_template_regions

  • id, template_id, region_key, region_type, label, font_family, lock_ratio, animation_json, default_content_json, read_only, x, y, width, height, z_index, created_at, created_by, modified_at, modified_by
  • Foreign key: - template_id -> c_templates.id with ON DELETE CASCADE

c_slides

  • id, title, template_id, content_json, thumbnail_path, created_at, created_by, modified_at, modified_by
  • Foreign key: - template_id -> c_templates.id with ON DELETE SET NULL

c_media_assets

  • id, media_path, original_name, media_type, mime_type, file_size, is_published, created_at, created_by, modified_at, modified_by
  • media_path is unique.
  • Indexed by media_type and created_at.

c_playlist_slides

  • id, playlist_id, slide_id, position, duration_seconds, use_video_duration, disable_audio, created_at, created_by, modified_at, modified_by
  • Foreign keys: - playlist_id -> c_playlists.id with ON DELETE CASCADE - slide_id -> c_slides.id with ON DELETE CASCADE

c_playlist_slide_schedule_rules

  • id, playlist_slide_id, position, start_datetime, end_datetime, start_time, end_time, schedule_days_json, created_at, created_by, modified_at, modified_by

  • Foreign key: - playlist_slide_id -> c_playlist_slides.id with ON DELETE CASCADE

  • Index: - (playlist_slide_id, position)

  • Each playlist slide can have zero or more schedule rules.

  • Rules are evaluated with OR across rows and with AND across the fields inside a single rule.

Devices

  • d_players - player registry and connection metadata.
  • d_screens - screen records and playlist assignment.
  • d_onboarding_devices - device-to-screen bindings and onboarded client names.

d_players

  • id, identifier, public_base_url, internal_base_url, last_seen_at, created_at, modified_at
  • id is the primary key.
  • identifier is unique and is the stable player identity used by the app.

d_screens

  • id, name, slug, playlist_id, created_at, created_by, modified_at, modified_by

  • slug is unique.

  • Foreign keys: - playlist_id -> c_playlists.id with ON DELETE SET NULL

  • Screens are no longer tied to a player foreign key directly; player registration and live connection metadata are tracked separately in d_players.

d_onboarding_devices

  • id, device_id, client_name, screen_id, created_at, created_by, modified_at, modified_by, last_seen_at
  • device_id is unique.
  • Foreign key: - screen_id -> d_screens.id with ON DELETE SET NULL

Onboarding

  • The onboarding flow uses d_onboarding_devices to bind a device to a screen and persist the client name.

Announcements

  • d_announcements - announcement content and display metadata.
  • d_announcement_screens - announcement-to-screen assignments.

d_announcements

  • id, message, short_label, announcement_type, color_key, icon_key, duration_seconds, expires_at, created_at, created_by, modified_at, modified_by
  • announcement_type defaults to lower-third.

d_announcement_screens

  • announcement_id, screen_id, created_at, created_by, modified_at, modified_by
  • Foreign keys: - announcement_id -> d_announcements.id with ON DELETE CASCADE - screen_id -> d_screens.id with ON DELETE CASCADE
  • Unique key: (announcement_id, screen_id)

Integrations

  • i_rss_feeds - RSS feed definitions and refresh cadence.
  • i_rss_feed_items - cached RSS feed items.
  • i_api_source_groups - API source names and shared authentication settings.
  • i_api_sources - named requests, independent refresh settings, and last response snapshots.
  • i_weather_locations - configured weather locations and last response snapshot.
  • i_timetable_groups - grouped timetable definitions used by the timetable region.
  • i_timetable_entries - dated entries that belong to a timetable group.

i_rss_feeds

  • id, name, feed_url, update_interval_value, update_interval_unit, item_limit, enabled, last_pulled_at, created_at, created_by, modified_at, modified_by

i_rss_feed_items

  • id, rss_feed_id, position, item_json, created_at, created_by, modified_at, modified_by
  • Foreign key: - rss_feed_id -> i_rss_feeds.id with ON DELETE CASCADE
  • Unique key: - (rss_feed_id, position)

i_api_source_groups

  • id, name, enabled, auth_method, auth_username, auth_password, auth_bearer_token, auth_header_name, auth_header_value, token_url, token_request_body_json, token_response_path, token_refresh_url, token_refresh_request_body_json, token_refresh_response_path, token_header_name, token_header_prefix, created_at, created_by, modified_at, modified_by
  • Unique group name; authentication applies to every child request. auth_method = 'none' supports public endpoints. Token caches are keyed by group and authentication configuration.
  • enabled is the source-wide refresh gate, added by migration 2.16.2 with a default of 1. Disabling a group leaves child enabled flags unchanged. Refresh lookups combine group/request flags, and queued work checks effective enabled state before making an outbound request.

i_api_sources

  • id, auth_source_id, name, api_url, request_method, request_body_json, items_path, update_interval_value, update_interval_unit, enabled, last_pulled_at, last_pull_error, last_response_status, last_response_content_type, last_response_json, created_at, created_by, modified_at, modified_by
  • auth_source_id references i_api_source_groups.id with restricted parent deletion. The web application requires at least one request per group and unique request names within each group.
  • Request IDs remain the source_id stored in slide regions and the keys used by scheduled refreshes. Removing a request used by a slide is blocked.
  • The 2.16.1 migration copies each existing source's authentication into a same-ID parent group and links the original request without changing its ID or response snapshot. Legacy authentication columns remain on request rows for schema compatibility but runtime authentication is loaded from the parent.
  • Editor/player request lists exclude authentication credentials. Refreshes resolve current parent authentication at execution time.

i_weather_locations

  • id, name, location_label, latitude, longitude, timezone, provider, temperature_unit, wind_unit, precipitation_unit, update_interval_value, update_interval_unit, enabled, last_pulled_at, last_pull_error, last_response_json, created_at, created_by, modified_at, modified_by
  • Stores configured weather locations and the most recent provider response used for forecast previews and weather regions.

i_timetable_groups

  • id, name, short_description, timezone, created_at, created_by, modified_at, modified_by
  • timezone defaults to Europe/London.

i_timetable_entries

  • id, schedule_group_id, title, short_description, start_datetime, end_datetime, created_at, created_by, modified_at, modified_by
  • Foreign key: - schedule_group_id -> i_timetable_groups.id with ON DELETE CASCADE
  • Index: - (schedule_group_id, start_datetime)

Operations

  • o_background_tasks - queue and history for background jobs.
  • o_app_state - generic app state and version markers stored as key/value pairs.
  • o_app_settings - administrator-configurable application settings stored by key.
  • o_audit_events - retained audit events for administrator activity and system changes.

o_background_tasks

  • id, task_key, task_type, title, category, status, payload_json, metadata_json, attempts, created_at, created_by, started_at, finished_at, error_message
  • Indexed by status, task_key, and task_type.

o_app_state

  • id, state_key, state_value, created_at, modified_at
  • state_key is unique.
  • schema_version is stored here so startup can detect the previously recorded schema version before deciding whether migrations need to run.

o_app_settings

  • id, setting_key, setting_value, created_at, created_by, modified_at, modified_by
  • setting_key is unique.
  • Values are stored as JSON and validated against the application setting definitions in src/data/app-settings.js.

o_audit_events

  • id, occurred_at, category, event_type, actor_user_id, target_type, target_id, target_label, ip_address, user_agent, details_json
  • Indexed by occurrence time, category and event type, actor, and target.

Notes

  • The schema is initialized with CREATE TABLE IF NOT EXISTS, so new installs can start from an empty database.
  • src/db/bootstrap.js seeds the canvas size defaults, default permissions, and the default administrator role.
  • src/db/migrations.js records the current schema version in o_app_state during startup so later launches can tell whether an update is happening.
  • The ER diagram shows declared foreign keys and the logical audit actor association; player registry and JSON-based references are intentionally not shown as foreign-key relationships.
erDiagram
        A_USERS {
        }
        A_ROLES {
        }
        A_PERMISSIONS {
        }
        A_ROLE_PERMISSIONS {
        }
        A_USER_ROLES {
        }
        A_SESSIONS {
        }
        A_ACCOUNTS {
        }
        A_VERIFICATIONS {
        }
        A_USER_TWO_FACTORS {
        }
        A_USER_PASSKEYS {
        }
        A_LOGIN_ATTEMPTS {
        }
        C_CANVAS_SIZES {
        }
        C_PLAYLISTS {
        }
        C_TEMPLATES {
        }
        C_TEMPLATE_REGIONS {
        }
        C_SLIDES {
        }
        C_MEDIA_ASSETS {
        }
        C_PLAYLIST_SLIDES {
        }
        C_PLAYLIST_SLIDE_SCHEDULE_RULES {
        }
        D_PLAYERS {
        }
        D_SCREENS {
        }
        D_ONBOARDING_DEVICES {
        }
        D_ANNOUNCEMENTS {
        }
        D_ANNOUNCEMENT_SCREENS {
        }
        I_RSS_FEEDS {
        }
        I_RSS_FEED_ITEMS {
        }
        I_API_SOURCES {
        }
        I_API_SOURCE_GROUPS {
        }
        I_WEATHER_LOCATIONS {
        }
        I_TIMETABLE_GROUPS {
        }
        I_TIMETABLE_ENTRIES {
        }
        O_APP_STATE {
        }
        O_APP_SETTINGS {
        }
        O_BACKGROUND_TASKS {
        }
        O_AUDIT_EVENTS {
        }

        A_USERS ||--o{ A_USER_ROLES : has
        A_ROLES ||--o{ A_USER_ROLES : assigned_to
        A_ROLES ||--o{ A_ROLE_PERMISSIONS : has
        A_PERMISSIONS ||--o{ A_ROLE_PERMISSIONS : granted_to
        A_USERS ||--o{ A_SESSIONS : owns
        A_USERS ||--o{ A_ACCOUNTS : has
        A_USERS ||--o| A_USER_TWO_FACTORS : secures_with
        A_USERS ||--o{ A_USER_PASSKEYS : registers
        A_USERS o|--o{ O_AUDIT_EVENTS : acts
        I_API_SOURCE_GROUPS ||--o{ I_API_SOURCES : contains

        C_CANVAS_SIZES o|--o{ C_TEMPLATES : used_by
        C_CANVAS_SIZES o|--o{ C_PLAYLISTS : used_by
        C_TEMPLATES ||--o{ C_TEMPLATE_REGIONS : contains
        C_TEMPLATES o|--o{ C_SLIDES : used_by
        C_MEDIA_ASSETS }o..o{ C_SLIDES : referenced_by
        C_MEDIA_ASSETS o{..o| C_TEMPLATES : background_for
        C_PLAYLISTS ||--o{ C_PLAYLIST_SLIDES : contains
        C_SLIDES ||--o{ C_PLAYLIST_SLIDES : included_in
        C_PLAYLIST_SLIDES ||--o{ C_PLAYLIST_SLIDE_SCHEDULE_RULES : has_rules

        C_PLAYLISTS o|--o{ D_SCREENS : uses
        D_SCREENS o|--o{ D_ONBOARDING_DEVICES : binds
        D_ANNOUNCEMENTS ||--o{ D_ANNOUNCEMENT_SCREENS : targets
        D_SCREENS ||--o{ D_ANNOUNCEMENT_SCREENS : receives

        I_RSS_FEEDS ||--o{ I_RSS_FEED_ITEMS : caches
        I_TIMETABLE_GROUPS ||--o{ I_TIMETABLE_ENTRIES : contains