5.4 KiB
10 — Admin users & organizations UI (agent 10/20)
Owns: /admin/users — users directory, company list with plan view/assign, platform staff role assignment.
Does not own: Billing plans table (agent 9), Permissions panel (agent 8), staff middleware/model (agent 6), admin shell chrome (agent 7 — nav label only).
Gate: requirePlatformAdmin (full admin / developer). support_staff is excluded.
PROBLEM
/admin/users was a thin client-filtered user list (no companies, no plan view, no staff role edits, no server pagination). Operators needed one place to manage orgs and staff after cutover.
Delivered
- Users tab — server-paginated (
limit/offset, page size 25), search (q), staff-only filter, set-password invite + local dev tools preserved. - Staff roles — dialog →
PATCH /api/admin/users/{id}/staff-rolewithadmin|developer|support_staff| clear. Uses agent 6 API; self-change blocked server-side. - Companies tab — paginated list with active plan name, legacy/public/custom badge, credits remaining/total, “without plan” filter.
- Assign plan — dialog lists all admin plans (ladder + legacy + custom) →
POST /api/admin/plans/assign. - API enrichments —
GET /api/admin/usersreturnstotal,staff_role,resolved_role, search/filters;GET /api/admin/companiesreturnstotal,plan_id/plan_name/plan_is_*, search. - Fix A1 catalog — Companies tab button + confirm →
POST /api/admin/companies/{id}/fix-catalog(prompt repair + hygiene; flash countsprompts/hashes/categories). - Clone catalog — existing sandbox clone flow (separate from Fix A1).
Source files
| File | Intent |
|---|---|
apps/web/src/routes/admin/users/+page.svelte |
Orgs UI (users + companies tabs) |
apps/web/src/lib/admin-orgs.ts |
Client helpers + types |
apps/api/internal/httpapi/admin_orgs_handlers.go |
Paginated list handlers |
apps/api/internal/httpapi/admin_staff_handlers.go |
Staff role PATCH (agent 6) |
apps/web/src/lib/components/AdminNav.svelte |
Nav label “Users & orgs” |
API contract
| Method | Path | Use |
|---|---|---|
GET |
/api/admin/users?limit&offset&q&staff_only&active_only&inactive_only |
User directory |
PATCH |
/api/admin/users/{id}/staff-role |
Body { "staff_role": "admin"|"developer"|"support_staff"|null } |
GET |
/api/admin/companies?limit&offset&q&without_active_plan |
Companies + plan summary |
GET |
/api/admin/plans |
Plan picker |
POST |
/api/admin/plans/assign |
{ company_id, plan_id } |
POST |
/api/admin/emails/set-password |
Existing invite tooling |
POST |
/api/admin/companies/{id}/fix-catalog |
In-place Fix A1 / catalog hygiene (confirm=true) |
POST |
/api/admin/companies/{id}/clone-catalog |
Clone source catalog into admin sandbox |
All under RequireSession + RequirePlatformAdmin (full admin only).
Fix A1 catalog (POST …/fix-catalog)
In-place hygiene for one company (prefer Platform Demo; A1 allowed with confirm). Never clears catalog, never mass-reprocesses, never uses A1 as a clone destination.
Body: { "confirm": true, "backfill_categories"?: true, "reprocess_sample_limit"?: 25 }
Sequence (processing.FixCompanyCatalog):
- Ensure
category_attributeslinks (orphan purge only) RepairCompanyCategoryEnhancePrompts— same map asRepairA1DemoCategoryEnhancePrompts/go run ./cmd/repair-category-prompts -apply- Re-apply BuiltIn
product_enhancetemplates (when AI prompts service is wired) FixCatalogHygieneWithIDs— weak enhance hashes, optional category backfill, description/meta hygiene- Sanitize
processed_attributes - Return recommended reprocess
raw_product_idsample (no auto reprocess)
Flash success (flash.admin.fixA1Success — keys admin.users.fixA1*):
| Flash param | result field |
Also as |
|---|---|---|
{prompts} |
prompts |
category_prompts_updated |
{hashes} |
hashes |
weak_hashes_cleared |
{categories} |
categories |
categories_backfilled |
{name} |
company name from UI row | company_name |
UI: Companies tab wrench → confirm dialog → fixAdminCompanyCatalog in $lib/admin-orgs.ts.
Security / performance
- AuthZ enforced server-side; UI gate is UX only.
- Cannot change own staff role (agent 6 handler).
- Lists use SQL
LIMIT/OFFSET+COUNT(*)— no full-table client filter. - Company plan join is a single query (no N+1).
ASSUMPTION: users.staff_role migration (029_staff_roles.sql) applied. UI shows an info banner if PATCH returns 404/501.
Manual verify
- Platform admin →
/admin/users. - Search users; toggle Staff only; page Next/Previous.
- Assign staff role (admin / developer / support_staff); confirm badge.
- Companies tab → see plan badge; filter Without plan; Assign plan (legacy + public).
- Companies tab → Fix A1 catalog on Platform Demo (or A1 with confirm); success flash shows prompts/hashes/categories counts.
- As
support_staff, confirm/admin/usersis forbidden (full-admin-only nav).
Coordination
- Agent 6: staff model +
PATCH …/staff-role+RequirePlatformAdminexcluding support_staff. - Agent 9: billing assign UX remains; orgs page is the directory-focused assign path.
- Agent 14: support_staff queue access after role grant from this UI.