|
|
|
@@ -0,0 +1,186 @@
|
|
|
|
|
package server
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"encoding/json"
|
|
|
|
|
"errors"
|
|
|
|
|
"net/http"
|
|
|
|
|
"strconv"
|
|
|
|
|
|
|
|
|
|
"chat-agent-system/internal/projectinstance"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleGetDefaultWorkflowPlan(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
|
|
|
if err != nil || projectID <= 0 {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"invalid project id"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if _, ok := s.requireProjectAccess(w, r, projectID); !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if s.instSvc == nil || !s.instSvc.Available() {
|
|
|
|
|
http.Error(w, `{"error":"ERR_NOT_AVAILABLE","message":"项目实例服务不可用"}`, http.StatusServiceUnavailable)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
workflowKey := r.URL.Query().Get("workflow_key")
|
|
|
|
|
version, _ := strconv.Atoi(r.URL.Query().Get("version"))
|
|
|
|
|
plan, err := s.instSvc.BuildDefaultPlan(r.Context(), workflowKey, version)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeProjectInstanceError(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeJSONResponse(w, http.StatusOK, plan)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleGetProjectSettings(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
|
|
|
if err != nil || projectID <= 0 {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"invalid project id"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
user, ok := s.requireProjectAccess(w, r, projectID)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if s.instSvc == nil || !s.instSvc.Available() {
|
|
|
|
|
http.Error(w, `{"error":"ERR_NOT_AVAILABLE","message":"项目实例服务不可用,请先执行数据库迁移"}`, http.StatusServiceUnavailable)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
isCreator, _ := s.pwSvc.IsProjectCreator(r.Context(), projectID, user.ID)
|
|
|
|
|
settings, err := s.instSvc.GetSettings(r.Context(), projectID, user.ID, isCreator)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeProjectInstanceError(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeJSONResponse(w, http.StatusOK, settings)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleSaveWorkflowPlan(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
|
|
|
if err != nil || projectID <= 0 {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"invalid project id"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
user, ok := s.requireProjectAccess(w, r, projectID)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if s.instSvc == nil || !s.instSvc.Available() {
|
|
|
|
|
http.Error(w, `{"error":"ERR_NOT_AVAILABLE","message":"项目实例服务不可用"}`, http.StatusServiceUnavailable)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
isCreator, _ := s.pwSvc.IsProjectCreator(r.Context(), projectID, user.ID)
|
|
|
|
|
var req projectinstance.SavePlanInput
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"无效的 JSON 请求体"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
settings, err := s.instSvc.SaveWorkflowPlan(r.Context(), projectID, user.ID, isCreator, req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeProjectInstanceError(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeJSONResponse(w, http.StatusOK, settings)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleActivationPreview(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
|
|
|
if err != nil || projectID <= 0 {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"invalid project id"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
user, ok := s.requireProjectAccess(w, r, projectID)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if s.instSvc == nil || !s.instSvc.Available() {
|
|
|
|
|
http.Error(w, `{"error":"ERR_NOT_AVAILABLE","message":"项目实例服务不可用"}`, http.StatusServiceUnavailable)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
isCreator, _ := s.pwSvc.IsProjectCreator(r.Context(), projectID, user.ID)
|
|
|
|
|
preview, err := s.instSvc.ActivationPreview(r.Context(), projectID, user.ID, isCreator)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeProjectInstanceError(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeJSONResponse(w, http.StatusOK, preview)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) handleActivateWorkflowInstance(w http.ResponseWriter, r *http.Request) {
|
|
|
|
|
projectID, err := strconv.ParseInt(r.PathValue("id"), 10, 64)
|
|
|
|
|
if err != nil || projectID <= 0 {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"invalid project id"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
user, ok := s.requireProjectAccess(w, r, projectID)
|
|
|
|
|
if !ok {
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
if s.instSvc == nil || !s.instSvc.Available() {
|
|
|
|
|
http.Error(w, `{"error":"ERR_NOT_AVAILABLE","message":"项目实例服务不可用"}`, http.StatusServiceUnavailable)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
isCreator, _ := s.pwSvc.IsProjectCreator(r.Context(), projectID, user.ID)
|
|
|
|
|
var req projectinstance.ActivateInput
|
|
|
|
|
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PARAM","message":"无效的 JSON 请求体"}`, http.StatusBadRequest)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
result, err := s.instSvc.Activate(r.Context(), projectID, user.ID, isCreator, req)
|
|
|
|
|
if err != nil {
|
|
|
|
|
writeProjectInstanceError(w, err)
|
|
|
|
|
return
|
|
|
|
|
}
|
|
|
|
|
writeJSONResponse(w, http.StatusOK, result)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func writeProjectInstanceError(w http.ResponseWriter, err error) {
|
|
|
|
|
switch {
|
|
|
|
|
case errors.Is(err, projectinstance.ErrForbiddenNotCreator):
|
|
|
|
|
http.Error(w, `{"error":"ERR_NOT_PROJECT_CREATOR","message":"仅项目创建人可配置工作流"}`, http.StatusForbidden)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrPlanFrozen):
|
|
|
|
|
http.Error(w, `{"error":"PLAN_FROZEN","message":"工作流已激活,规划已冻结"}`, http.StatusConflict)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrWorkflowAlreadyActive):
|
|
|
|
|
http.Error(w, `{"error":"WORKFLOW_ALREADY_ACTIVE","message":"项目已有运行中的工作流实例"}`, http.StatusConflict)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrPlanChangedReconfirm):
|
|
|
|
|
http.Error(w, `{"error":"PLAN_CHANGED_RECONFIRM","message":"规划已变更,请重新预览并确认"}`, http.StatusConflict)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrValidationFailed):
|
|
|
|
|
http.Error(w, `{"error":"VALIDATION_FAILED","message":"`+err.Error()+`"}`, http.StatusBadRequest)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrTokenInvalid):
|
|
|
|
|
http.Error(w, `{"error":"TOKEN_INVALID","message":"激活令牌无效或已过期"}`, http.StatusBadRequest)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrConcurrentConflict):
|
|
|
|
|
http.Error(w, `{"error":"ERR_CONCURRENT_SWITCH","message":"项目版本冲突,请刷新后重试"}`, http.StatusConflict)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrPlanRevisionConflict):
|
|
|
|
|
http.Error(w, `{"error":"PLAN_REVISION_CONFLICT","message":"规划版本冲突,请刷新后重试"}`, http.StatusConflict)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrProjectNotFound):
|
|
|
|
|
http.Error(w, `{"error":"ERR_PROJECT_NOT_FOUND","message":"项目不存在"}`, http.StatusNotFound)
|
|
|
|
|
case errors.Is(err, projectinstance.ErrInvalidPlan):
|
|
|
|
|
http.Error(w, `{"error":"ERR_INVALID_PLAN","message":"`+err.Error()+`"}`, http.StatusBadRequest)
|
|
|
|
|
default:
|
|
|
|
|
http.Error(w, `{"error":"ERR_INTERNAL","message":"`+err.Error()+`"}`, http.StatusInternalServerError)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func (s *Server) blockLegacyWorkflowSwitch(w http.ResponseWriter, r *http.Request, projectID int64) bool {
|
|
|
|
|
if s.instSvc == nil || !s.instSvc.Available() {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
live, err := s.instSvc.HasLiveInstance(r.Context(), projectID)
|
|
|
|
|
if err != nil {
|
|
|
|
|
http.Error(w, `{"error":"ERR_INTERNAL","message":"`+err.Error()+`"}`, http.StatusInternalServerError)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
if live {
|
|
|
|
|
http.Error(w, `{"error":"WORKFLOW_ALREADY_ACTIVE","message":"项目已有运行中的工作流实例,请使用项目设置查看流程"}`, http.StatusConflict)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
proj, err := s.pwSvc.GetProject(r.Context(), projectID, 0)
|
|
|
|
|
if err != nil {
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
if proj != nil && proj.LifecycleStatus == "planning" && proj.ActiveWorkflowKey == "" {
|
|
|
|
|
http.Error(w, `{"error":"LEGACY_SWITCH_BLOCKED","message":"新项目请通过项目设置完成工作流激活"}`, http.StatusConflict)
|
|
|
|
|
return true
|
|
|
|
|
}
|
|
|
|
|
return false
|
|
|
|
|
}
|