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.
This commit is contained in:
@@ -0,0 +1,140 @@
|
||||
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})
|
||||
}
|
||||
Reference in New Issue
Block a user