Files
greeneclipse 8580c996c3 Initial commit of Descrybe v2 without local scratch artifacts.
Drop one-shot tmp/axe scripts and agent i18n scratch so the Gitea tree is deployable.
2026-08-09 22:47:43 +02:00

141 lines
3.8 KiB
Go

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})
}