screentinker/server
ScreenTinker b255f2bfe1 Resolve proof-of-play references instead of trusting the reported id
Players replay a cached playlist, so the id reported on play_start can outlive the
row it names. play_logs.content_id carries a foreign key to content(id), and the id
went straight into the INSERT — so deleting a piece of content made every subsequent
play of it throw, and the whole event was discarded by a catch that logged no
identifiers. On production this fired roughly 360 times in six hours and wrote zero
rows in 24h: Reports was recording nothing at all, for everyone.

Widgets had a quieter version of the same bug. play_logs.widget_id exists and was
never written, so a widget play could not be attributed even when it did insert, and
play_end matched on content_id alone and so could never close a widget's open row.

The reported id is now looked up before use and written to whichever column it
belongs to. An id matching neither degrades to null references rather than losing the
event — content_name still records what played. A play event for a device that does
not exist is still refused; that foreign key is a real invariant, not an obstacle.

play_end matches on either column, and breaks ties on id: started_at has second
granularity, so two plays inside one second tie on it and the wrong row could be
closed. The new tests caught exactly that as flakiness before it was pinned.

The catch now logs the event, device, content and zone. Without them this was
undiagnosable in production.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 21:15:44 -05:00
..
config fix(logging): gate CF-Connecting-IP on a Cloudflare peer, not any trusted proxy 2026-07-26 10:23:04 -05:00
db feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00
lib Scope device serialization to what each endpoint actually needs 2026-07-27 20:40:40 -05:00
middleware Merge branch 'fix/recovery-grants' into release/auth-campaign 2026-07-26 16:22:59 -05:00
player fix(widgets): honest webpage-widget note — blocked sites don't work on device (#230) 2026-07-24 19:17:00 -05:00
routes Scope device serialization to what each endpoint actually needs 2026-07-27 20:40:40 -05:00
scripts fix(content+android): rotation-aware media — portrait upright on dashboard AND player (#170) (#172) 2026-07-12 22:05:11 -05:00
services feat(auth): self-service password reset 2026-07-27 11:19:39 -05:00
test Resolve proof-of-play references instead of trusting the reported id 2026-07-27 21:15:44 -05:00
ws Resolve proof-of-play references instead of trusting the reported id 2026-07-27 21:15:44 -05:00
.gitignore feat(email): Microsoft Graph send + alert spam protection + preferences UI 2026-05-12 18:16:40 -05:00
config.js feat(email): SMTP transport as an alternative to Microsoft Graph [#173] (#179) 2026-07-13 15:56:22 -05:00
package-lock.json Update sharp to 0.35.x, and repair the corrupt PNG fixture it exposed 2026-07-27 21:15:44 -05:00
package.json Update sharp to 0.35.x, and repair the corrupt PNG fixture it exposed 2026-07-27 21:15:44 -05:00
server.js Merge branch 'feat/self-service-password-reset' 2026-07-27 11:21:36 -05:00
version.js chore(version): single-source VERSION, env-configurable data paths, bump tooling 2026-06-10 12:56:03 -05:00