package httpapi import ( "encoding/json" "errors" "net/http" "strings" "github.com/descrybe/descrybe-v2/apps/api/internal/auth" "github.com/descrybe/descrybe-v2/apps/api/internal/support" "github.com/go-chi/chi/v5" "github.com/google/uuid" ) // handleAdminListStaff returns platform staff users (admin|developer only). // GET /api/admin/staff func (s *Server) handleAdminListStaff(w http.ResponseWriter, r *http.Request) { if s.Auth == nil { Error(w, http.StatusServiceUnavailable, "auth unavailable") return } limit, offset := ParseLimitOffset(r) users, err := s.Auth.ListStaffUsers(r.Context(), limit, offset) if err != nil { Error(w, http.StatusInternalServerError, "list failed") return } JSON(w, http.StatusOK, map[string]any{ "staff": users, "limit": limit, "offset": offset, }) } // handleAdminSetStaffRole assigns or clears a platform staff role (admin|developer only). // PATCH /api/admin/users/{id}/staff-role // Body: {"staff_role":"admin"|"developer"|"support_staff"|null} func (s *Server) handleAdminSetStaffRole(w http.ResponseWriter, r *http.Request) { if s.Auth == nil { Error(w, http.StatusServiceUnavailable, "auth unavailable") return } actorID, ok := UserIDFromContext(r.Context()) if !ok { Error(w, http.StatusUnauthorized, "unauthorized") return } targetID, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } if targetID == actorID { Error(w, http.StatusForbidden, "cannot change own staff role") return } var body struct { StaffRole *string `json:"staff_role"` } dec := json.NewDecoder(r.Body) dec.DisallowUnknownFields() if err := dec.Decode(&body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } role := "" if body.StaffRole != nil { role = strings.TrimSpace(*body.StaffRole) } user, err := s.Auth.SetStaffRole(r.Context(), targetID, role) if err != nil { if errors.Is(err, auth.ErrInvalidStaffRole) { Error(w, http.StatusBadRequest, "invalid staff_role") return } if errors.Is(err, auth.ErrStaffUserNotFound) { Error(w, http.StatusNotFound, "user not found") return } Error(w, http.StatusInternalServerError, "update failed") return } JSON(w, http.StatusOK, map[string]any{ "user": user, "staff_capabilities": auth.StaffCapabilities(user.ResolvedRole), }) } // handleAdminSetSupportAgent grants or revokes support_staff only (full-admin exclusive). // PUT /api/admin/support/agents/{id} // Body: {"enabled": true|false} or {"is_support_agent": true|false} func (s *Server) handleAdminSetSupportAgent(w http.ResponseWriter, r *http.Request) { if s.Support == nil { Error(w, http.StatusServiceUnavailable, "support unavailable") return } actorID, ok := UserIDFromContext(r.Context()) if !ok { Error(w, http.StatusUnauthorized, "unauthorized") return } targetID, err := uuid.Parse(chi.URLParam(r, "id")) if err != nil { Error(w, http.StatusBadRequest, "invalid id") return } if targetID == actorID { Error(w, http.StatusForbidden, "cannot change own support agent flag") return } var body struct { Enabled *bool `json:"enabled"` IsSupportAgent *bool `json:"is_support_agent"` } if err := DecodeJSON(r, &body); err != nil { Error(w, http.StatusBadRequest, "invalid json") return } enable := false switch { case body.Enabled != nil: enable = *body.Enabled case body.IsSupportAgent != nil: enable = *body.IsSupportAgent default: Error(w, http.StatusBadRequest, "enabled required") return } agent, err := s.Support.SetSupportAgent(r.Context(), targetID, enable) if errors.Is(err, support.ErrNotFound) { Error(w, http.StatusNotFound, "user not found") return } if err != nil { LogAndError(w, http.StatusInternalServerError, "update failed", err) return } JSON(w, http.StatusOK, map[string]any{"agent": agent}) }