- 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 (baseline44161d2areproduced DATA RACE; now green under -race) - goose 00071 duplicate (dialogue merge1da17a28brought 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)
195 lines
5.7 KiB
Go
195 lines
5.7 KiB
Go
package server
|
|
|
|
import (
|
|
"context"
|
|
"encoding/json"
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"chat-agent-system/internal/auth"
|
|
"chat-agent-system/internal/workflowdef"
|
|
)
|
|
|
|
func (s *Server) requireWorkflowAdmin(w http.ResponseWriter, r *http.Request) (*auth.UserAccount, bool) {
|
|
user, ok := s.requireSystemAdmin(w, r)
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
if s.wfDefSvc() == nil || !s.wfDefSvc().Available() {
|
|
writeSafeError(w, r, http.StatusServiceUnavailable, "工作流定义服务需要数据库 IAM")
|
|
return nil, false
|
|
}
|
|
return user, true
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkflows(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := s.requireWorkflowAdmin(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
switch r.Method {
|
|
case http.MethodGet:
|
|
defs, err := s.wfDefSvc().ListDefinitions(r.Context())
|
|
if err != nil {
|
|
writeSafeError(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, map[string]any{"workflows": defs})
|
|
case http.MethodPost:
|
|
var req workflowdef.CreateDefinitionInput
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
writeSafeError(w, r, http.StatusBadRequest, "无效的 JSON")
|
|
return
|
|
}
|
|
def, ver, err := s.wfDefSvc().CreateDefinition(r.Context(), req, user.ID)
|
|
if err != nil {
|
|
status := http.StatusBadRequest
|
|
if errors.Is(err, workflowdef.ErrValidationFailed) {
|
|
status = http.StatusUnprocessableEntity
|
|
}
|
|
writeSafeError(w, r, status, err.Error())
|
|
return
|
|
}
|
|
writeJSONResponse(w, http.StatusCreated, map[string]any{"definition": def, "version": ver})
|
|
default:
|
|
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkflowByID(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := s.requireWorkflowAdmin(w, r); !ok {
|
|
return
|
|
}
|
|
id, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil || id <= 0 {
|
|
writeSafeError(w, r, http.StatusBadRequest, "invalid workflow id")
|
|
return
|
|
}
|
|
if r.Method != http.MethodGet {
|
|
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
def, err := s.wfDefSvc().GetDefinition(r.Context(), id)
|
|
if err != nil {
|
|
writeSafeError(w, r, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
versions, err := s.wfDefSvc().ListVersions(r.Context(), id)
|
|
if err != nil {
|
|
writeSafeError(w, r, http.StatusInternalServerError, err.Error())
|
|
return
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, map[string]any{"definition": def, "versions": versions})
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkflowVersion(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := s.requireWorkflowAdmin(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
workflowID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
if err != nil || workflowID <= 0 {
|
|
writeSafeError(w, r, http.StatusBadRequest, "invalid workflow id")
|
|
return
|
|
}
|
|
version, err := strconv.Atoi(r.PathValue("version"))
|
|
if err != nil || version <= 0 {
|
|
writeSafeError(w, r, http.StatusBadRequest, "invalid version")
|
|
return
|
|
}
|
|
|
|
switch r.Method {
|
|
case http.MethodPatch, http.MethodPut:
|
|
var body struct {
|
|
Definition workflowdef.VersionDefinition `json:"definition"`
|
|
}
|
|
if err := json.NewDecoder(r.Body).Decode(&body); err != nil {
|
|
writeSafeError(w, r, http.StatusBadRequest, "invalid json")
|
|
return
|
|
}
|
|
updated, err := s.wfDefSvc().UpdateDraft(r.Context(), workflowID, version, body.Definition)
|
|
if err != nil {
|
|
status := http.StatusBadRequest
|
|
if errors.Is(err, workflowdef.ErrNotDraft) {
|
|
status = http.StatusConflict
|
|
}
|
|
writeSafeError(w, r, status, err.Error())
|
|
return
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, updated)
|
|
default:
|
|
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
|
}
|
|
_ = user
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkflowValidate(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := s.requireWorkflowAdmin(w, r); !ok {
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
workflowID, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
version, _ := strconv.Atoi(r.PathValue("version"))
|
|
res, err := s.wfDefSvc().ValidateVersion(r.Context(), workflowID, version)
|
|
if err != nil {
|
|
writeSafeError(w, r, http.StatusNotFound, err.Error())
|
|
return
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, res)
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkflowPublish(w http.ResponseWriter, r *http.Request) {
|
|
user, ok := s.requireWorkflowAdmin(w, r)
|
|
if !ok {
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
workflowID, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
version, _ := strconv.Atoi(r.PathValue("version"))
|
|
ver, err := s.wfDefSvc().PublishVersion(r.Context(), workflowID, version, user.ID)
|
|
if err != nil {
|
|
status := http.StatusBadRequest
|
|
if errors.Is(err, workflowdef.ErrValidationFailed) {
|
|
status = http.StatusUnprocessableEntity
|
|
}
|
|
writeSafeError(w, r, status, err.Error())
|
|
return
|
|
}
|
|
if refresher, ok := s.pwSvc().Catalog().(interface {
|
|
Refresh(context.Context) error
|
|
}); ok {
|
|
_ = refresher.Refresh(r.Context())
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, ver)
|
|
}
|
|
|
|
func (s *Server) handleAdminWorkflowRetire(w http.ResponseWriter, r *http.Request) {
|
|
if _, ok := s.requireWorkflowAdmin(w, r); !ok {
|
|
return
|
|
}
|
|
if r.Method != http.MethodPost {
|
|
writeSafeError(w, r, http.StatusMethodNotAllowed, "method not allowed")
|
|
return
|
|
}
|
|
workflowID, _ := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
version, _ := strconv.Atoi(r.PathValue("version"))
|
|
ver, err := s.wfDefSvc().RetireVersion(r.Context(), workflowID, version)
|
|
if err != nil {
|
|
writeSafeError(w, r, http.StatusBadRequest, err.Error())
|
|
return
|
|
}
|
|
if refresher, ok := s.pwSvc().Catalog().(interface {
|
|
Refresh(context.Context) error
|
|
}); ok {
|
|
_ = refresher.Refresh(r.Context())
|
|
}
|
|
writeJSONResponse(w, http.StatusOK, ver)
|
|
}
|