Skip to content

Commit 9f0653e

Browse files
rajbosCopilot
andcommitted
feat(sharing-server): add admin token usage dashboard
- New /admin route (admin-only, redirects non-admins to /dashboard) - Per-user summary table showing 30d input/output tokens, interactions, days active and last upload date - Overview stat cards for 7d / 30d / 90d with period toggle: total users, active users, total tokens, avg tokens per active user, interactions - Stacked trend chart (top-10 users + Others) with period toggle (7d / 30d / 90d) and mode toggle (Total / Per-User Average) Per-User Average mode shows a single-series bar: total tokens that day divided by the number of active users that day - Admin Panel link in the user dashboard header for admin users - New DB functions getAdminUserSummaries() and getAdminDailyTotals() using LEFT JOIN so zero-upload users still appear in the table - New idx_uploads_day index for efficient admin-wide day-range queries Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
1 parent 7c809df commit 9f0653e

2 files changed

Lines changed: 426 additions & 1 deletion

File tree

sharing-server/src/db.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,27 @@ export interface UserRow {
1414
fluency_score_json: string | null;
1515
}
1616

17+
export interface UserUsageSummary {
18+
user_id: number;
19+
github_login: string;
20+
github_name: string | null;
21+
avatar_url: string | null;
22+
is_admin: number;
23+
total_input: number;
24+
total_output: number;
25+
total_interactions: number;
26+
days_active: number;
27+
last_upload_day: string | null;
28+
}
29+
30+
export interface AdminDailyRow {
31+
day: string;
32+
github_login: string;
33+
input_tokens: number;
34+
output_tokens: number;
35+
interactions: number;
36+
}
37+
1738
export interface UploadRow {
1839
id: number;
1940
user_id: number;
@@ -208,6 +229,7 @@ function initSchema(db: DatabaseSync): void {
208229
db.exec(`
209230
CREATE INDEX IF NOT EXISTS idx_uploads_user_day ON usage_uploads(user_id, day);
210231
CREATE INDEX IF NOT EXISTS idx_uploads_dataset ON usage_uploads(dataset_id, day);
232+
CREATE INDEX IF NOT EXISTS idx_uploads_day ON usage_uploads(day);
211233
`);
212234

213235
// Add fluency_json column if it doesn't exist (migration for existing DBs)
@@ -358,3 +380,48 @@ export function closeDb(): void {
358380
_db = undefined;
359381
}
360382
}
383+
384+
/**
385+
* Per-user aggregate stats for the admin dashboard.
386+
* LEFT JOIN ensures users with no uploads in the period still appear (with zeros).
387+
* `last_upload_day` reflects their all-time last upload, not filtered to the period.
388+
*/
389+
export function getAdminUserSummaries(days: number): UserUsageSummary[] {
390+
const cutoff = new Date();
391+
cutoff.setUTCDate(cutoff.getUTCDate() - days);
392+
const cutoffStr = cutoff.toISOString().slice(0, 10);
393+
return getDb().prepare(`
394+
SELECT
395+
u.id AS user_id,
396+
u.github_login,
397+
u.github_name,
398+
u.avatar_url,
399+
u.is_admin,
400+
COALESCE(SUM(CASE WHEN uu.day >= ? THEN uu.input_tokens ELSE 0 END), 0) AS total_input,
401+
COALESCE(SUM(CASE WHEN uu.day >= ? THEN uu.output_tokens ELSE 0 END), 0) AS total_output,
402+
COALESCE(SUM(CASE WHEN uu.day >= ? THEN uu.interactions ELSE 0 END), 0) AS total_interactions,
403+
COUNT(DISTINCT CASE WHEN uu.day >= ? THEN uu.day END) AS days_active,
404+
MAX(uu.day) AS last_upload_day
405+
FROM users u
406+
LEFT JOIN usage_uploads uu ON uu.user_id = u.id
407+
GROUP BY u.id
408+
ORDER BY total_input + total_output DESC
409+
`).all(cutoffStr, cutoffStr, cutoffStr, cutoffStr) as unknown as UserUsageSummary[];
410+
}
411+
412+
/** Daily per-user token totals for the admin trend chart. */
413+
export function getAdminDailyTotals(days: number): AdminDailyRow[] {
414+
return getDb().prepare(`
415+
SELECT
416+
uu.day,
417+
u.github_login,
418+
SUM(uu.input_tokens) AS input_tokens,
419+
SUM(uu.output_tokens) AS output_tokens,
420+
SUM(uu.interactions) AS interactions
421+
FROM usage_uploads uu
422+
JOIN users u ON u.id = uu.user_id
423+
WHERE uu.day >= date('now', '-' || ? || ' days')
424+
GROUP BY uu.day, u.github_login
425+
ORDER BY uu.day, u.github_login
426+
`).all(days) as unknown as AdminDailyRow[];
427+
}

0 commit comments

Comments
 (0)