User admin in the web UI, admin-only log, unread-first OPML feeds

- Settings > Manage users: add an account (password, or none for proxy
  sign-in), toggle admin, remove. Backed by GET/POST /api/users and
  PATCH/DELETE /api/users/{id}, 403 for non-admins. The only admin
  cannot be demoted or removed.
- GET /api/logs is admin-only and the Log button is hidden for others;
  the log names every account, feed and failed sign-in.
- Feeds inside an OPML list those with unread items first, in the
  sidebar folder and on the subscription's page.
- Deploying is now buildx --push to 192.168.1.130:5000 and recreating
  the ipodderx service of the Arcane project content; CLAUDE.md and the
  README's Docker section say so.
- Tests: Playwright for user admin, the last-admin guard, 403s for a
  non-admin and the unread ordering (new Aardvark Radio fixture); a unit
  test for last_admin; the smoke test drives usersModal.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173mGu6rK18Ne7UGTwAaVJV
This commit is contained in:
2026-09-11 12:43:28 +00:00
parent 6114add4a6
commit 5e95557cbb
11 changed files with 370 additions and 40 deletions

View File

@@ -44,7 +44,9 @@ const ctx = {
json: () => Promise.resolve(
String(url).includes('/api/settings')
? { schedule: 'every 60m', every_mins: 60, download_dir: '/tmp', max_total_gb: 0, max_age_days: 0 }
: []),
: String(url).includes('/api/users')
? [{ id: 1, name: 'admin', admin: true, password: true }, { id: 2, name: 'sam', admin: false, password: false }]
: []),
}),
EventSource: function () { this.close = () => {}; },
MediaMetadata: function () {},
@@ -79,6 +81,7 @@ const drive = [
['downloadLatestModal', () => ctx.downloadLatestModal(feed)],
['removeFeed', () => ctx.removeFeed(feed)],
['prefsModal', () => ctx.prefsModal()],
['usersModal', () => ctx.usersModal()],
['logsModal', () => ctx.logsModal()],
// `const S` is not reachable from here: top-level const/let do not become properties
// of a vm context the way var and function declarations do.