feat: user management — admin create, password reset, archive/delete, quick invite

Phase 1: must_change_password enforcement + change password endpoint/page
Phase 2: Admin user creation (M365-style) with temp password
Phase 3: Password reset (self-service forgot + admin-triggered)
Phase 4: User archive (soft delete) + hard delete with precheck
Phase 5: Quick invite from admin Users page

Also fixes:
- Auto-create subscription for accounts missing one
- Hard delete precheck ignores sole-member personal accounts
- Seed script patches tree nodes for validation compliance

Migrations: 031 (must_change_password), 032 (password_reset_tokens), 033 (user soft delete)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
chihlasm
2026-02-13 01:42:51 -05:00
parent b8f25f19eb
commit ad59446332
32 changed files with 3064 additions and 38 deletions

View File

@@ -16,6 +16,8 @@ import type {
InviteCodeResponse,
InviteCodeCreateRequest,
UserDetailResponse,
AdminUserCreate,
AdminUserCreateResponse,
} from '@/types/admin'
export const adminApi = {
@@ -25,7 +27,9 @@ export const adminApi = {
getDashboardActivity: () =>
api.get<ActivityEntry[]>('/admin/dashboard/activity').then(r => r.data),
// Users (existing endpoints)
// Users
createUser: (data: AdminUserCreate) =>
api.post<AdminUserCreateResponse>('/admin/users', data).then(r => r.data),
listUsers: (params?: Record<string, unknown>) =>
api.get('/admin/users', { params }).then(r => r.data),
getUser: (id: string) =>
@@ -41,6 +45,24 @@ export const adminApi = {
moveUserAccount: (id: string, display_code: string) =>
api.put(`/admin/users/${id}/move-account`, { display_code }).then(r => r.data),
// Users - archive & delete
archiveUser: (id: string) =>
api.put(`/admin/users/${id}/archive`).then(r => r.data),
restoreUser: (id: string) =>
api.put(`/admin/users/${id}/restore`).then(r => r.data),
hardDeleteCheck: (id: string) =>
api.get<{ can_delete: boolean; blockers: Record<string, number> }>(`/admin/users/${id}/hard-delete-check`).then(r => r.data),
hardDeleteUser: (id: string) =>
api.delete(`/admin/users/${id}/hard-delete`),
// Users - quick invite
createInvite: (data: { email: string; account_display_code: string; role: string }) =>
api.post<{ id: string; email: string; code: string; role: string; account_display_code: string; email_sent: boolean }>('/admin/invites', data).then(r => r.data),
// Users - password reset
adminResetPassword: (id: string, mode: 'email_link' | 'temp_password') =>
api.post<{ message: string; temporary_password?: string; email_sent: boolean }>(`/admin/users/${id}/password-reset`, { mode }).then(r => r.data),
// Users - detail + subscription
getUserDetail: (id: string) =>
api.get<UserDetailResponse>(`/admin/users/${id}`).then(r => r.data),