Admin step B: stats endpoint + /admin dashboard

- users.is_admin (+ migration); admin = is_admin OR email in GOODNEWS_ADMIN_EMAILS
  (normalized). is_admin exposed on /api/auth/me. Server-authorized GET
  /api/admin/stats (403 for non-admins).
- queries.admin_stats: visitors (today/7d/30d), returning vs one-and-done, top
  opened articles, popular groupings + topics (derived from article_id at query
  time), share breakdown, daily opens/visits trend — all aggregate, no PII.
- /admin page (gated, redirects non-admins): stat cards, CSS bar lists, a daily
  trend; "Admin dashboard" link on /account for admins. 129 tests pass.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
jay
2026-06-03 18:25:46 +00:00
parent 1a778e1334
commit 762f121320
7 changed files with 334 additions and 2 deletions
+2
View File
@@ -29,6 +29,7 @@
<a href="/?view=saved">Saved</a>
<a href="/?open=history">History</a>
<a href="/?open=boundaries">Boundaries</a>
{#if auth.user.is_admin}<a href="/admin" class="admin">Admin dashboard</a>{/if}
</nav>
<AccountPanel onclose={() => goto('/')} />
{/if}
@@ -53,4 +54,5 @@
border-radius: 999px; padding: 7px 15px; font-size: 0.9rem;
}
.quick a:hover { border-color: var(--accent); color: var(--accent-deep); }
.quick a.admin { border-color: var(--accent); color: var(--accent-deep); }
</style>