Files
descrybe/docs/admin-roles-support/10-admin-orgs-ui.md
T
2026-08-16 16:57:36 +02:00

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

  1. Users tab — server-paginated (limit/offset, page size 25), search (q), staff-only filter, set-password invite + local dev tools preserved.
  2. Staff roles — dialog → PATCH /api/admin/users/{id}/staff-role with admin | developer | support_staff | clear. Uses agent 6 API; self-change blocked server-side.
  3. Companies tab — paginated list with active plan name, legacy/public/custom badge, credits remaining/total, “without plan” filter.
  4. Assign plan — dialog lists all admin plans (ladder + legacy + custom) → POST /api/admin/plans/assign.
  5. API enrichmentsGET /api/admin/users returns total, staff_role, resolved_role, search/filters; GET /api/admin/companies returns total, plan_id / plan_name / plan_is_*, search.
  6. Fix A1 catalog — Companies tab button + confirm → POST /api/admin/companies/{id}/fix-catalog (prompt repair + hygiene; flash counts prompts/hashes/categories).
  7. 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):

  1. Ensure category_attributes links (orphan purge only)
  2. RepairCompanyCategoryEnhancePrompts — same map as RepairA1DemoCategoryEnhancePrompts / go run ./cmd/repair-category-prompts -apply
  3. Re-apply BuiltIn product_enhance templates (when AI prompts service is wired)
  4. FixCatalogHygieneWithIDs — weak enhance hashes, optional category backfill, description/meta hygiene
  5. Sanitize processed_attributes
  6. Return recommended reprocess raw_product_id sample (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

  1. Platform admin → /admin/users.
  2. Search users; toggle Staff only; page Next/Previous.
  3. Assign staff role (admin / developer / support_staff); confirm badge.
  4. Companies tab → see plan badge; filter Without plan; Assign plan (legacy + public).
  5. Companies tab → Fix A1 catalog on Platform Demo (or A1 with confirm); success flash shows prompts/hashes/categories counts.
  6. As support_staff, confirm /admin/users is forbidden (full-admin-only nav).

Coordination

  • Agent 6: staff model + PATCH …/staff-role + RequirePlatformAdmin excluding 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.