Commit graph

12 commits

Author SHA1 Message Date
ScreenTinker 52dd44a3e8 Add group-level scheduling, group playlist assignment, and persist audio unlock
Phase 4 group scheduling: schema migration adds group_id to schedules with
CHECK constraint, scheduler evaluates group+device schedules with priority,
group deletion converts schedules to per-device copies. Dashboard gets
playlist assignment dropdown and current playlist label on group headers.
Player persists audio unlock state in localStorage so version reloads
don't lose audio on unattended displays.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-15 20:22:42 -05:00
ScreenTinker 436a3be7f6 Phase 3: playlist publish/draft state with auto-publish from device detail
Schema: add status and published_snapshot columns to playlists table.
Migration snapshots all existing playlists as published (idempotent via schema_migrations).

Devices always receive the published_snapshot, not live playlist_items.
Edits from device-detail/groups auto-publish immediately (display updates instantly).
Edits from playlist detail page go to draft (requires explicit publish).
POST /playlists/:id/publish snapshots and pushes to all devices.
POST /playlists/:id/discard reverts playlist_items from published snapshot.
Content deletion scrubs references from all published snapshots.

Frontend: draft badge in playlist list, prominent yellow banner with publish/discard
buttons on playlist detail and device detail pages.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-13 20:52:29 -05:00
ScreenTinker afbe113acf Security audit remediation: auth, IDOR, XSS, hardening
- Device WebSocket authentication: devices get a device_token on
  registration, must present it on reconnect. All WS events require
  prior auth. Timing-safe token comparison.
- IDOR fixes: ownership checks on schedules (device, week), layouts
  (all CRUD, zones, duplicate, device assign), video-walls (content,
  device-config).
- XSS prevention: shared esc() helper in utils.js, fixed 13 innerHTML
  injection points across 9 frontend files.
- OAuth hardening: no longer silently overwrites auth_provider on
  accounts with local passwords (returns 409).
- JWT pinned to HS256 for sign and verify.
- Password policy: change endpoint now requires 8 chars (was 6).
- HSTS header enabled (max-age 1 year, includeSubDomains).
- Stripe webhook rejects unsigned payloads when no secret configured.
- Screenshot size validation (max 2MB base64).
- Rate limiting on exports, imports, content operations.
- Content file serving checks playlist_items instead of old assignments.
- Content ownership verified in device-groups assign-content.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:48:07 -05:00
ScreenTinker b87904c326 Add schema_migrations table for run-once migration tracking
New schema_migrations table (id TEXT PK, ran_at INTEGER) tracks which
one-time migrations have executed. The Phase 2 playlist migration now
checks for 'phase2_playlist_migration' in this table instead of
inferring state from devices.playlist_id. Records the migration ID
after successful completion. Eliminates ffprobe overhead on subsequent
startups.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:28:10 -05:00
ScreenTinker df7919f84f Migration: add detailed logging for device, item, and video probe counts
Reports devices migrated, total playlist items created, videos probed
via ffprobe, and existing schedules. Helps verify the migration ran
correctly on first startup.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:25:42 -05:00
ScreenTinker 1483500458 Probe video durations during migration and v1 import
Migration (database.js): switched from sync execFileSync to async execFile
with promise wrapper, matching the pattern in playlists.js. Probes each
video content item, backfills content.duration_sec, and uses the real
duration in playlist_items. Falls back to the assignment's original
duration_sec if the probe fails or content isn't a video.

V1 import (status.js): moved assignment-to-playlist conversion out of the
synchronous db.transaction() so async ffprobe can run. Content files are
already on disk from the transaction, so probing works. Same fallback logic.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:24:56 -05:00
ScreenTinker aee6766c4c Migration: probe video durations with ffprobe during assignment-to-playlist conversion
Videos were getting the default 10s from the assignments table. Now ffprobe
runs for each video content item during migration, backfills the content
table, and uses the real duration in playlist_items.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:21:40 -05:00
ScreenTinker c8dffab5ad Phase 2 migration: convert existing assignments to per-device playlists
Runs once at startup. For each device with assignments, creates an
auto-generated playlist (is_auto_generated=1) containing those items
and sets device.playlist_id. Skips if any device already has a playlist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:03:41 -05:00
ScreenTinker 2af3cec8a6 Phase 2 schema: add playlist_id to devices/schedules, is_auto_generated to playlists
Every device will point to exactly one playlist. Schedules can temporarily
override a device's playlist. Auto-generated playlists (from migration) are
flagged so the UI can filter them.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 22:00:56 -05:00
ScreenTinker 1fbeccff7c Add playlists and playlist_items tables to schema
Phase 1 of playlist refactor: standalone playlist entities with ordered
items. No changes to existing tables or display behavior.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-11 21:09:12 -05:00
ScreenTinker e7081a579c Fix widget assignments, designer scaling, and cache strategy
- Make assignments.content_id nullable so widgets can be assigned to playlists
- Fix designer publish to use vw units matching preview (was hardcoded px)
- Add px-to-vw conversion in text widget renderer for backward compat
- Fix webpage widget zoom scaling
- Add widget rendering support in fullscreen player mode
- Set no-cache headers on JS/CSS/HTML for instant updates (ETag/304)
- Set 30-day cache on media files and uploaded content for Cloudflare

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 16:25:05 -05:00
ScreenTinker 1594a9d4a4 Initial open source release
ScreenTinker - open source digital signage management software.
MIT License, all features included, no license gates.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-04-08 12:14:53 -05:00