feat(admin): per-user profile view + rebuild action (#81 phase B.1)
Surfaces phase A's profile features in /admin/users/:id so we can verify they're actually computing useful values before investing in bandit consumption. The detail GET now includes profile rows joined with registry metadata (name, value, age, fresh badge, ttlSec, description). Read does NOT trigger compute — staleness must be visible. A new POST .../profile/rebuild button force-recomputes and is audit-logged like reset-bandit. Refs #81. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -36,12 +36,24 @@ export interface AdminUser {
|
||||
deletedAt: string | null;
|
||||
}
|
||||
|
||||
export interface ProfileFeatureView {
|
||||
name: string;
|
||||
value: number | string | null;
|
||||
updatedAt: string | null;
|
||||
ageSec: number | null;
|
||||
fresh: boolean;
|
||||
ttlSec: number;
|
||||
dtype: 'numeric' | 'categorical';
|
||||
description: string;
|
||||
}
|
||||
|
||||
export interface AdminUserDetail {
|
||||
user: AdminUser;
|
||||
integrations: { provider: string; connectedAt: string }[];
|
||||
tipsServed: number;
|
||||
lastTipAt: string | null;
|
||||
recentFeedback: { id: string; action: string; createdAt: string; tipId: string }[];
|
||||
profile: ProfileFeatureView[];
|
||||
}
|
||||
|
||||
export interface AuditAction {
|
||||
@@ -135,6 +147,13 @@ export function resetBandit(userId: string) {
|
||||
});
|
||||
}
|
||||
|
||||
export function rebuildUserProfile(userId: string) {
|
||||
return apiFetch<{ ok: boolean; profile: ProfileFeatureView[] }>(
|
||||
`/admin/users/${userId}/profile/rebuild`,
|
||||
{ method: 'POST' },
|
||||
);
|
||||
}
|
||||
|
||||
export function getAuditLog(limit = 50, offset = 0) {
|
||||
return apiFetch<{ actions: AuditAction[]; total: number }>(
|
||||
`/admin/audit?limit=${limit}&offset=${offset}`,
|
||||
|
||||
Reference in New Issue
Block a user