feat(stats): per-user API quota attribution + admin usage page
Track who burned how much YouTube API quota. A QuotaEvent audit log (migration 0009) records every spend with the triggering user (NULL = background/system) and an action label, set via a request/job-scoped contextvar (quota.attribute) so no call signatures change. User-initiated work (sync subscriptions, unsubscribe, opt-in recent backfill, manual enrich) attributes to the user; scheduler work to System, split by action. - backend: QuotaEvent model + migration 0009; quota.attribute() contextvar; record_usage logs events; entry points wrapped (routes/sync, routes/channels, scheduler); GET /api/quota/my-usage + GET /api/quota/admin - frontend: admin-only Stats page (header nav, page=stats) with daily bars + per-user breakdown by action and range picker; 'Your API usage' in Settings -> Sync for every user Verified: attribution + endpoints compute correctly; events are per-user vs System.
This commit is contained in:
parent
bcc4371ac7
commit
f255728f75
15 changed files with 451 additions and 26 deletions
|
|
@ -165,6 +165,27 @@ export interface MyStatus {
|
|||
paused: boolean;
|
||||
}
|
||||
|
||||
export interface MyUsage {
|
||||
today: number;
|
||||
last_7d: number;
|
||||
last_30d: number;
|
||||
all_time: number;
|
||||
by_action: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface AdminQuotaRow {
|
||||
user_id: number | null;
|
||||
email: string;
|
||||
total: number;
|
||||
by_action: Record<string, number>;
|
||||
}
|
||||
|
||||
export interface AdminQuota {
|
||||
range_days: number;
|
||||
rows: AdminQuotaRow[];
|
||||
daily: { day: string; total: number }[];
|
||||
}
|
||||
|
||||
export interface ManagedChannel {
|
||||
id: string;
|
||||
title: string | null;
|
||||
|
|
@ -215,6 +236,10 @@ export const api = {
|
|||
req(`/api/channels/${id}/subscription`, { method: "DELETE" }),
|
||||
syncSubscriptions: () => req("/api/sync/subscriptions", { method: "POST" }),
|
||||
|
||||
// --- quota usage ---
|
||||
myUsage: (): Promise<MyUsage> => req("/api/quota/my-usage"),
|
||||
adminQuota: (days = 30): Promise<AdminQuota> => req(`/api/quota/admin?days=${days}`),
|
||||
|
||||
// --- onboarding / admin ---
|
||||
requestAccess: (email: string): Promise<{ status: string }> =>
|
||||
req("/auth/request-access", { method: "POST", body: JSON.stringify({ email }) }),
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue