Staff push notifications, and revalidate embedded assets

Notifications: staff turn them on per device from a new Notifications page,
pick each type (bans, gags, mutes and silences added or lifted, server down
and back up) and quiet hours. A watcher polls SimpleAdmin's tables, so in-game
penalties are sent too. Web Push with VAPID and RFC 8291 encryption in
internal/push, standard library only. Tables in sql/push.sql, created at
startup; the VAPID key is made on first start and kept in saw_settings.
Penalty notifications link to the public list searching for the player
(#bans?q=<steamid64>). The Android app delegates notifications (versionCode 3).

Caching: pages, scripts and styles are served with Cache-Control: no-cache
and an ETag, so a deploy shows up at once instead of after Cloudflare's
default 4-hour browser cache.
This commit is contained in:
Astra 2026-10-03 13:49:40 +01:00
parent 7900bf5b3e
commit 17232bd5a2
23 changed files with 2135 additions and 12 deletions

46
sql/push.sql Normal file
View file

@ -0,0 +1,46 @@
-- Push notifications for staff: the panel's own tables, not SimpleAdmin's.
--
-- The panel creates these itself at startup (this file is embedded in the binary), so running it
-- by hand is only needed if the panel's database user can't CREATE TABLE.
--
-- saw_push_devices: one row per browser or app that turned notifications on. endpoint is the push
-- service URL the browser gave, endpoint_hash its SHA-256 (hex) for the unique key, p256dh and
-- auth the browser's keys for encrypting messages to it. Rows are deleted when the push service
-- says the subscription is gone.
--
-- saw_push_prefs: what each staff member wants, for all their devices. types is a comma-separated
-- list of notification types (ban, unban, gag, ungag, ...). Quiet hours are minutes after midnight
-- in their own timezone (an IANA name, from their browser); start after end wraps past midnight.
--
-- saw_settings: panel settings. vapid_private_key is the key push services know the panel by;
-- changing or deleting it stops every device getting notifications until it turns them on again.
CREATE TABLE IF NOT EXISTS `saw_push_devices` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`steamid` bigint(20) NOT NULL,
`endpoint` varchar(1024) NOT NULL,
`endpoint_hash` char(64) NOT NULL,
`p256dh` varchar(128) NOT NULL,
`auth` varchar(64) NOT NULL,
`user_agent` varchar(255) NOT NULL DEFAULT '',
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `uq_push_devices_endpoint` (`endpoint_hash`),
KEY `idx_push_devices_steamid` (`steamid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `saw_push_prefs` (
`steamid` bigint(20) NOT NULL,
`types` varchar(512) NOT NULL DEFAULT '',
`quiet_enabled` tinyint(1) NOT NULL DEFAULT 0,
`quiet_start` smallint(6) NOT NULL DEFAULT 1380,
`quiet_end` smallint(6) NOT NULL DEFAULT 480,
`timezone` varchar(64) NOT NULL DEFAULT 'UTC',
PRIMARY KEY (`steamid`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `saw_settings` (
`name` varchar(64) NOT NULL,
`value` text NOT NULL,
PRIMARY KEY (`name`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;