// api_users.go — JSON handlers for the user-management surface. // // All endpoints in this file are admin-only; gating happens at the // route-mount site (server.go's admin band). package http import ( "encoding/json" "log/slog" stdhttp "net/http" ) type listUsersResponse struct { Users []apiUser `json:"users"` } type apiUser struct { ID string `json:"id"` Username string `json:"username"` Role string `json:"role"` Email *string `json:"email,omitempty"` Disabled bool `json:"disabled"` MustChangePassword bool `json:"must_change_password"` CreatedAt string `json:"created_at"` LastLoginAt *string `json:"last_login_at,omitempty"` } func (s *Server) handleAPIUsersList(w stdhttp.ResponseWriter, r *stdhttp.Request) { users, err := s.deps.Store.ListUsers(r.Context()) if err != nil { slog.Error("api users: list", "err", err) writeJSONError(w, stdhttp.StatusInternalServerError, "internal", err.Error()) return } out := make([]apiUser, len(users)) for i, u := range users { var lastLogin *string if u.LastLoginAt != nil { s := u.LastLoginAt.UTC().Format("2006-01-02T15:04:05Z") lastLogin = &s } out[i] = apiUser{ ID: u.ID, Username: u.Username, Role: string(u.Role), Email: u.Email, Disabled: u.DisabledAt != nil, MustChangePassword: u.MustChangePassword, CreatedAt: u.CreatedAt.UTC().Format("2006-01-02T15:04:05Z"), LastLoginAt: lastLogin, } } w.Header().Set("Content-Type", "application/json; charset=utf-8") _ = json.NewEncoder(w).Encode(listUsersResponse{Users: out}) }