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_byusernameis unique.emailis 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
passwordon thecredentialprovider account using Better Auth's native scrypt format; legacy PBKDF2 hashes upgrade after successful sign-in. Password hashes are not stored ona_users. - Foreign key:
-
user_id->a_users.idwithON 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_untiluser_idis unique; each user has at most one authenticator record.- Foreign key:
user_id->a_users.idwithON DELETE CASCADE.
a_user_passkeys
id,name,public_key,user_id,credential_id,counter,device_type,backed_up,transports,created_at,aaguidcredential_idis unique;user_idis indexed.- Foreign key:
user_id->a_users.idwithON DELETE CASCADE.
a_login_attempts
id,rate_key,failed_count,last_requestrate_keyis unique.
a_roles
id,role_key,name,description,require_two_factor,created_at,created_by,modified_at,modified_byrole_keyis unique.nameis unique.require_two_factordefaults 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_atpermission_keyis 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_bysession_hashis 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.idwithON DELETE SET NULL
c_templates
id,name,canvas_size_id,background_image_path,background_color,background_gradient,created_at,created_by,modified_at,modified_bybackground_gradientstores normalized linear gradient settings as JSON text, including angle and colour stops.- Foreign key:
-
canvas_size_id->c_canvas_sizes.idwithON 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.idwithON 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.idwithON 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_bymedia_pathis unique.- Indexed by
media_typeandcreated_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.idwithON DELETE CASCADE-slide_id->c_slides.idwithON 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.idwithON 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_atidis the primary key.identifieris 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 -
slugis unique. -
Foreign keys: -
playlist_id->c_playlists.idwithON 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_atdevice_idis unique.- Foreign key:
-
screen_id->d_screens.idwithON DELETE SET NULL
Onboarding
- The onboarding flow uses
d_onboarding_devicesto 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_byannouncement_typedefaults tolower-third.
d_announcement_screens
announcement_id,screen_id,created_at,created_by,modified_at,modified_by- Foreign keys:
-
announcement_id->d_announcements.idwithON DELETE CASCADE-screen_id->d_screens.idwithON 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.idwithON 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. enabledis the source-wide refresh gate, added by migration 2.16.2 with a default of1. 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_byauth_source_idreferencesi_api_source_groups.idwith 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_idstored 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_bytimezonedefaults toEurope/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.idwithON 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, andtask_type.
o_app_state
id,state_key,state_value,created_at,modified_atstate_keyis unique.schema_versionis 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_bysetting_keyis 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.jsseeds the canvas size defaults, default permissions, and the default administrator role.src/db/migrations.jsrecords the current schema version ino_app_stateduring 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