The platform admin's last shell-only jobs are endpoints
Suspend or reinstate a company (PATCH /api/admin/clients/{id}), reset
its owner's password (shown once), and delete it - and an owner can
remove a shop opened by mistake (DELETE /api/sites/{site}, empty only).
Suspension ends every session the company holds in the same
transaction: login and ingest already refused an inactive client, but a
live access token would have kept reading for up to twelve hours, so
'suspend' would have meant 'suspend some time tomorrow'. Deletion is
deliberately two steps - the company must already be suspended and the
request repeats the slug - because the data under it is biometric.
Face images go first (a storage failure aborts with nothing touched),
then the broker logins, then the rows by cascade.
Exercised against the local Postgres and broker: create, open a shop,
remove it (two plugin commands), refuse delete while active, suspend
(owner's token 401 immediately), reset, delete, zero rows left.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KGcjxF1cNLcuwc3DAPcnfj
This commit is contained in:
@@ -137,6 +137,28 @@ type Store interface {
|
||||
// --- platform administration ---
|
||||
CreateClientWithOwner(ctx context.Context, in NewClientInput) (NewClientResult, error)
|
||||
ListClients(ctx context.Context) ([]ClientRow, error)
|
||||
// SetClientActive suspends or reinstates a company. Suspending revokes every
|
||||
// session its users hold in the same transaction - login and ingest already
|
||||
// refuse an inactive client, but a live access token would otherwise keep
|
||||
// reading for up to twelve hours. Returns the slug and how many sessions
|
||||
// were ended.
|
||||
SetClientActive(ctx context.Context, clientID string, active bool) (ClientRow, int, error)
|
||||
// ClientOwners lists the active owners of a company, for a platform admin
|
||||
// resetting one of their passwords.
|
||||
ClientOwners(ctx context.Context, clientID string) ([]TeamMember, error)
|
||||
// ClientImageKeys is every face image a company holds - the first step of
|
||||
// deleting the company, for the same reason it is the first step of erasing
|
||||
// a person: once the rows are gone nothing knows which objects to remove.
|
||||
ClientImageKeys(ctx context.Context, clientID string) ([]string, error)
|
||||
// DeleteClient removes a SUSPENDED company and everything under it, and
|
||||
// returns the broker usernames of its sites so their logins can be removed.
|
||||
// Refuses an active company: suspension first is what makes this a
|
||||
// two-step decision instead of one click.
|
||||
DeleteClient(ctx context.Context, clientID string) (ClientRow, []string, error)
|
||||
// DeleteEmptySite removes a shop that has no visits and no cameras - the
|
||||
// one opened by mistake - and returns its broker username. A shop with
|
||||
// history is closed, not deleted.
|
||||
DeleteEmptySite(ctx context.Context, clientID, siteID string) (string, error)
|
||||
|
||||
// --- enrolment ---
|
||||
RedeemEnrolment(ctx context.Context, hash []byte) (Enrolment, error)
|
||||
@@ -278,6 +300,7 @@ func (s *Server) Routes() *http.ServeMux {
|
||||
mux.HandleFunc("GET /api/reports/conversion", s.authed(s.handleConversion))
|
||||
mux.HandleFunc("GET /api/sites", s.authed(s.handleSites))
|
||||
mux.HandleFunc("POST /api/sites", s.authed(s.handleCreateSite))
|
||||
mux.HandleFunc("DELETE /api/sites/{site}", s.authed(s.handleDeleteSite))
|
||||
|
||||
// Cameras, onboarded from head office. The shop PC still does the
|
||||
// connecting - it is the only thing on the camera's network - so these
|
||||
@@ -322,6 +345,9 @@ func (s *Server) Routes() *http.ServeMux {
|
||||
// because creating the first admin cannot require being signed in as one.
|
||||
mux.HandleFunc("GET /api/admin/clients", s.adminOnly(s.handleListClients))
|
||||
mux.HandleFunc("POST /api/admin/clients", s.adminOnly(s.handleCreateClient))
|
||||
mux.HandleFunc("PATCH /api/admin/clients/{id}", s.adminOnly(s.handleSetClientActive))
|
||||
mux.HandleFunc("POST /api/admin/clients/{id}/owner-password", s.adminOnly(s.handleResetOwnerPassword))
|
||||
mux.HandleFunc("DELETE /api/admin/clients/{id}", s.adminOnly(s.handleDeleteClient))
|
||||
|
||||
// Not session-authenticated: this is how a PC with no credentials gets
|
||||
// some. The enrolment token is the credential.
|
||||
|
||||
Reference in New Issue
Block a user