Files
Agent-System/internal/server/admin_system_handlers.go
T
weichen a9c9a9182e refactor(server): remove 24 mirrored Runtime fields via nil-safe accessors; fix Shutdown-vs-background-loop DATA RACE and goose 00071 duplicate version
- Server no longer mirrors app.Runtime services (gov/svc/pwSvc/...): 574 call sites now use accessors in runtime_accessors.go
- backgroundLoop (mutex+sync.Once) stops sweeper/publisher/reminder/wsGC goroutines race-free (baseline 44161d2a reproduced DATA RACE; now green under -race)
- goose 00071 duplicate (dialogue merge 1da17a28 brought two 00071): context-conflict-adjudications renumbered to 00072; dev DB applied only through 00070 so zero data migration
- task_finalize tests build minimal app.Runtime; test injection via serverTestOverrides + setOverride* (explicit-nil markers for fail-closed cases)
2026-09-25 13:17:22 +00:00

139 lines
4.7 KiB
Go

package server
import (
"encoding/json"
"errors"
"net/http"
"strconv"
"chat-agent-system/internal/authz"
"chat-agent-system/internal/sysadmin"
)
// 系统管理台:细粒度 system:* 权限(phase-2:管理员 all-or-nothing)。
func (s *Server) handleAdminSystemOverview(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireSystemPermission(w, r, authz.PermSystemOverview); !ok {
return
}
if s.sysadmin() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "sysadmin service unavailable")
return
}
writeJSONResponse(w, http.StatusOK, s.sysadmin().Overview(r.Context()))
}
func (s *Server) handleAdminSystemKB(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireSystemPermission(w, r, authz.PermSystemKBRead); !ok {
return
}
if s.sysadmin() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "sysadmin service unavailable")
return
}
writeJSONResponse(w, http.StatusOK, s.sysadmin().KBInfo())
}
func (s *Server) handleAdminSystemFeatures(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireSystemPermission(w, r, authz.PermSystemFeaturesRead); !ok {
return
}
if s.sysadmin() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "sysadmin service unavailable")
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{"features": s.sysadmin().Features()})
}
func (s *Server) handleAdminSystemSettings(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireSystemPermission(w, r, authz.PermSystemSettingsRead); !ok {
return
}
if s.sysadmin() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "sysadmin service unavailable")
return
}
settings, err := s.sysadmin().ListSettings(r.Context())
if err != nil {
writeSafeError(w, r, http.StatusInternalServerError, err.Error())
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{"settings": settings})
}
func (s *Server) handleAdminSystemSettingByKey(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireSystemPermission(w, r, authz.PermSystemSettingsWrite)
if !ok {
return
}
if s.sysadmin() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "sysadmin service unavailable")
return
}
key := r.PathValue("key")
var req struct {
Value json.RawMessage `json:"value"`
ExpectedRevision *int64 `json:"expected_revision"`
}
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeSafeError(w, r, http.StatusBadRequest, "无效的请求体")
return
}
setting, err := s.sysadmin().UpdateSettingEx(r.Context(), sysadmin.SettingUpdate{
Key: key,
Value: req.Value,
UpdatedBy: user.ID,
ExpectedRevision: req.ExpectedRevision,
RequestID: r.Header.Get("X-Request-ID"),
})
switch {
case errors.Is(err, sysadmin.ErrUnknownSetting):
writeSafeError(w, r, http.StatusBadRequest, "未知的设置键")
case errors.Is(err, sysadmin.ErrSettingReadOnly):
writeSafeError(w, r, http.StatusForbidden, "该设置为只读")
case errors.Is(err, sysadmin.ErrInvalidSettingValue):
writeSafeError(w, r, http.StatusBadRequest, "设置值类型不匹配")
case errors.Is(err, sysadmin.ErrExpectedRevisionRequired):
writeSafeError(w, r, http.StatusBadRequest, "该设置需要 expected_revision")
case errors.Is(err, sysadmin.ErrSettingRevisionConflict):
writeSafeError(w, r, http.StatusConflict, `{"error":"SETTING_REVISION_CONFLICT","message":"设置版本冲突,请刷新后重试"}`)
case errors.Is(err, sysadmin.ErrStoreUnavailable):
writeSafeError(w, r, http.StatusServiceUnavailable, "设置存储不可用")
case err != nil:
writeSafeError(w, r, http.StatusInternalServerError, err.Error())
default:
writeJSONResponse(w, http.StatusOK, setting)
}
}
func (s *Server) handleAdminSystemSettingAudits(w http.ResponseWriter, r *http.Request) {
if _, ok := s.requireSystemPermission(w, r, authz.PermSystemAuditRead); !ok {
return
}
if s.sysadmin() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "sysadmin service unavailable")
return
}
key := r.PathValue("key")
limit := 50
if raw := r.URL.Query().Get("limit"); raw != "" {
n, err := strconv.Atoi(raw)
if err != nil || n < 0 {
writeSafeError(w, r, http.StatusBadRequest, "无效的 limit")
return
}
limit = n
}
audits, err := s.sysadmin().ListPolicyAudits(r.Context(), key, limit)
switch {
case errors.Is(err, sysadmin.ErrUnknownSetting):
writeSafeError(w, r, http.StatusBadRequest, "未知的设置键")
case errors.Is(err, sysadmin.ErrStoreUnavailable):
writeSafeError(w, r, http.StatusServiceUnavailable, "设置存储不可用")
case err != nil:
writeSafeError(w, r, http.StatusInternalServerError, err.Error())
default:
writeJSONResponse(w, http.StatusOK, map[string]any{"audits": audits})
}
}