Adding a context parameter to the shared package put its call chains in
contextcheck's scope; the flagged background context in the provider
setup is deliberate since provider lifetime exceeds any request.
The admin set-admin-flag, set-status and delete-user operations were
implemented twice — once in the v1 echo handlers, once in the v2 Huma handlers.
Extract the load/guard/mutate logic into models.SetUserAdminFlag,
models.SetUserStatusAsAdmin and models.DeleteUserAsAdmin so both APIs call the
same code; each handler keeps only its own request binding, validation and
response shape. v1 stays byte-identical on the wire.
The patch-admin, patch-status and delete-user handlers each repeated the same
session open/load/commit/rollback scaffold. Extract it into adminMutateUser,
which owns the transaction and takes a closure for each handler's distinct
guard-and-write step.
Port the admin action endpoints to the Huma-backed /api/v2:
- GET /admin/overview instance counts + license snapshot
- POST /admin/users create a user (201)
- PATCH /admin/users/{id}/admin promote/demote (*bool, nil = unchanged)
- PATCH /admin/users/{id}/status set status (*Status, nil = unchanged)
- DELETE /admin/users/{id} delete (mode=now|scheduled, 204)
- PATCH /admin/projects/{id}/owner reassign project owner
All sit behind the existing gateV2AdminRoutes path middleware (admin + license
gate, 404 on failure), so no per-handler permission checks are added. The
hand-registered PATCH routes carry genuine partial semantics, which AutoPatch
does not synthesise. The admin user response reuses the existing
pkg/routes/api/shared package.