Files
Agent-System/internal/server/handlers_context.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

134 lines
4.1 KiB
Go

package server
import (
"encoding/json"
"net/http"
"strconv"
"chat-agent-system/internal/ctxmgr"
)
// -----------------------------------------------------------------------------
// 多级上下文与偏好记忆 RESTful 控制器
// -----------------------------------------------------------------------------
// handleGetUserContext 获取单用户私有上下文与个性化偏好
// user_id 仅允许等于当前认证用户本人(或系统管理员),不再默认回落 user id=1。
func (s *Server) handleGetUserContext(w http.ResponseWriter, r *http.Request) {
user, ok := s.requirePrincipal(w, r)
if !ok {
return
}
userIDStr := r.URL.Query().Get("user_id")
projectIDStr := r.URL.Query().Get("project_id")
sessionID := r.URL.Query().Get("session_id")
userID, _ := strconv.ParseInt(userIDStr, 10, 64)
if userID <= 0 {
userID = user.ID
}
if userID != user.ID && !s.isSystemAdminUser(r.Context(), user) {
writeJSONResponse(w, http.StatusForbidden, map[string]string{
"error_code": "ERR_FORBIDDEN",
"message": "无权查询其他用户的上下文",
})
return
}
projectID, _ := strconv.ParseInt(projectIDStr, 10, 64)
if s.ctxMgr() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized")
return
}
uCtx, err := s.ctxMgr().GetUserContext(r.Context(), projectID, userID, sessionID)
if err != nil {
writeSafeError(w, r, http.StatusInternalServerError, "get user context failed: "+err.Error())
return
}
writeJSONResponse(w, http.StatusOK, uCtx)
}
// handleUpdateUserPreference 定期整理或前端保存用户习惯偏好
func (s *Server) handleUpdateUserPreference(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
var req UpdateUserPreferenceRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
writeSafeError(w, r, http.StatusBadRequest, "invalid request body: "+err.Error())
return
}
if req.UserID <= 0 {
req.UserID = user.ID
}
if req.UserID != user.ID && !s.isSystemAdminUser(r.Context(), user) {
writeJSONResponse(w, http.StatusForbidden, map[string]string{
"error_code": "ERR_FORBIDDEN",
"message": "无权修改其他用户的偏好",
})
return
}
if s.ctxMgr() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized")
return
}
pref := &ctxmgr.UserPreference{
UserID: req.UserID,
CommunicationStyle: req.CommunicationStyle,
FocusAreas: req.FocusAreas,
OutputFormatPref: req.OutputFormatPref,
CustomInstructions: req.CustomInstructions,
}
if err := s.ctxMgr().UpdateUserPreference(r.Context(), pref); err != nil {
writeSafeError(w, r, http.StatusInternalServerError, "save user preference failed: "+err.Error())
return
}
writeJSONResponse(w, http.StatusOK, map[string]any{"status": "ok", "user_id": req.UserID})
}
// handleGetProjectContext 获取项目级多方协同事实黑板与里程碑决策流
func (s *Server) handleGetProjectContext(w http.ResponseWriter, r *http.Request) {
user, ok := s.requireAuthenticatedUser(w, r)
if !ok {
return
}
projectIDStr := r.URL.Query().Get("project_id")
projectID, _ := strconv.ParseInt(projectIDStr, 10, 64)
if projectID <= 0 {
writeSafeError(w, r, http.StatusBadRequest, "missing or invalid project_id query param")
return
}
// Membership first: allowAnyStageAccess assumes the caller already passed
// project membership (see ResolveStageAccess). Without this guard any
// authenticated user can read an activated project's collaborative context.
if !s.enforceChatProjectAccess(w, r, user, projectID) {
return
}
if !s.allowAnyStageAccess(r.Context(), projectID, user.ID) {
s.writeWorkflowAccessDenied(w)
return
}
if s.ctxMgr() == nil {
writeSafeError(w, r, http.StatusServiceUnavailable, "context manager not initialized")
return
}
pCtx, err := s.ctxMgr().GetProjectContext(r.Context(), projectID)
if err != nil {
writeSafeError(w, r, http.StatusInternalServerError, "get project context failed: "+err.Error())
return
}
writeJSONResponse(w, http.StatusOK, pCtx)
}