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:
+2
-1
@@ -46,7 +46,8 @@ def normalize_email(email: str) -> str:
|
||||
|
||||
def get_user(conn: sqlite3.Connection, user_id: int) -> sqlite3.Row | None:
|
||||
return conn.execute(
|
||||
"SELECT id, email, display_name, avatar_url, created_at FROM users WHERE id = ?", (user_id,)
|
||||
"SELECT id, email, display_name, avatar_url, is_admin, created_at FROM users WHERE id = ?",
|
||||
(user_id,),
|
||||
).fetchone()
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user