chore: make codes the repository root
This commit is contained in:
241
internal/scenario/handler.go
Normal file
241
internal/scenario/handler.go
Normal file
@@ -0,0 +1,241 @@
|
||||
package scenario
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"net/http"
|
||||
"regexp"
|
||||
"strconv"
|
||||
|
||||
"git.iwork-ai.com/xdc/iqudo-top1/internal/audit"
|
||||
"git.iwork-ai.com/xdc/iqudo-top1/internal/auth"
|
||||
"git.iwork-ai.com/xdc/iqudo-top1/internal/model"
|
||||
"git.iwork-ai.com/xdc/iqudo-top1/internal/response"
|
||||
"github.com/gin-gonic/gin"
|
||||
"gorm.io/datatypes"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
type Handler struct {
|
||||
db *gorm.DB
|
||||
}
|
||||
|
||||
func NewHandler(db *gorm.DB) *Handler {
|
||||
return &Handler{db: db}
|
||||
}
|
||||
|
||||
type scenarioInput struct {
|
||||
Name string `json:"name" binding:"required,max=128"`
|
||||
Industry string `json:"industry" binding:"required,max=64"`
|
||||
RoleName string `json:"role_name" binding:"required,max=64"`
|
||||
Goal string `json:"goal" binding:"required,max=2000"`
|
||||
TriggerText string `json:"trigger_text" binding:"required,max=2000"`
|
||||
Visibility string `json:"visibility" binding:"omitempty,oneof=private team tenant"`
|
||||
}
|
||||
|
||||
type fieldInput struct {
|
||||
FieldKey string `json:"field_key" binding:"required,max=64"`
|
||||
FieldName string `json:"field_name" binding:"required,max=128"`
|
||||
FieldType string `json:"field_type" binding:"required,oneof=text textarea number boolean select multiselect date"`
|
||||
Required bool `json:"required"`
|
||||
Options json.RawMessage `json:"options"`
|
||||
Validation json.RawMessage `json:"validation"`
|
||||
SortOrder int `json:"sort_order"`
|
||||
}
|
||||
|
||||
var fieldKeyPattern = regexp.MustCompile(`^[A-Za-z][A-Za-z0-9_]*$`)
|
||||
|
||||
func (h *Handler) List(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
var items []model.Scenario
|
||||
query := h.db.Where("tenant_id = ? AND status <> ?", p.TenantID, "archived")
|
||||
if keyword := c.Query("keyword"); keyword != "" {
|
||||
query = query.Where("name LIKE ? OR industry LIKE ?", "%"+keyword+"%", "%"+keyword+"%")
|
||||
}
|
||||
if status := c.Query("status"); status != "" {
|
||||
query = query.Where("status = ?", status)
|
||||
}
|
||||
var total int64
|
||||
if err := query.Model(&model.Scenario{}).Count(&total).Error; err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询场景失败")
|
||||
return
|
||||
}
|
||||
if err := query.Order("updated_at DESC").Find(&items).Error; err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询场景失败")
|
||||
return
|
||||
}
|
||||
response.OK(c, gin.H{"items": items, "total": total})
|
||||
}
|
||||
|
||||
func (h *Handler) Create(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
var input scenarioInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "场景信息不完整")
|
||||
return
|
||||
}
|
||||
visibility := input.Visibility
|
||||
if visibility == "" {
|
||||
visibility = "tenant"
|
||||
}
|
||||
item := model.Scenario{TenantID: p.TenantID, Name: input.Name, Industry: input.Industry, RoleName: input.RoleName, Goal: input.Goal, TriggerText: input.TriggerText, Visibility: visibility, Status: "draft", CreatedBy: p.UserID}
|
||||
if err := h.db.Create(&item).Error; err != nil {
|
||||
response.Error(c, http.StatusInternalServerError, "CREATE_FAILED", "创建场景失败")
|
||||
return
|
||||
}
|
||||
_ = audit.Record(h.db, p, "create", "scenario", item.ID, input)
|
||||
response.Created(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) Get(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
id, ok := idParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var item model.Scenario
|
||||
if err := h.db.Where("id = ? AND tenant_id = ?", id, p.TenantID).First(&item).Error; err != nil {
|
||||
notFound(c, err, "场景不存在")
|
||||
return
|
||||
}
|
||||
var fields []model.ScenarioField
|
||||
var sops []model.SOP
|
||||
h.db.Where("scenario_id = ? AND tenant_id = ?", id, p.TenantID).Order("sort_order, id").Find(&fields)
|
||||
h.db.Where("scenario_id = ? AND tenant_id = ?", id, p.TenantID).Order("updated_at DESC").Find(&sops)
|
||||
response.OK(c, gin.H{"scenario": item, "fields": fields, "sops": sops})
|
||||
}
|
||||
|
||||
func (h *Handler) Update(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
id, ok := idParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input scenarioInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "场景信息不完整")
|
||||
return
|
||||
}
|
||||
updates := map[string]interface{}{"name": input.Name, "industry": input.Industry, "role_name": input.RoleName, "goal": input.Goal, "trigger_text": input.TriggerText, "visibility": input.Visibility}
|
||||
result := h.db.Model(&model.Scenario{}).Where("id = ? AND tenant_id = ? AND status <> ?", id, p.TenantID, "archived").Updates(updates)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
response.Error(c, http.StatusNotFound, "NOT_FOUND", "场景不存在")
|
||||
return
|
||||
}
|
||||
_ = audit.Record(h.db, p, "update", "scenario", id, input)
|
||||
h.Get(c)
|
||||
}
|
||||
|
||||
func (h *Handler) Archive(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
id, ok := idParam(c, "id")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result := h.db.Model(&model.Scenario{}).Where("id = ? AND tenant_id = ?", id, p.TenantID).Update("status", "archived")
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
response.Error(c, http.StatusNotFound, "NOT_FOUND", "场景不存在")
|
||||
return
|
||||
}
|
||||
_ = audit.Record(h.db, p, "archive", "scenario", id, nil)
|
||||
response.OK(c, gin.H{"id": id})
|
||||
}
|
||||
|
||||
func (h *Handler) CreateField(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
scenarioID, ok := idParam(c, "id")
|
||||
if !ok || !h.scenarioExists(p.TenantID, scenarioID) {
|
||||
if ok {
|
||||
response.Error(c, http.StatusNotFound, "NOT_FOUND", "场景不存在")
|
||||
}
|
||||
return
|
||||
}
|
||||
var input fieldInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "字段配置不正确")
|
||||
return
|
||||
}
|
||||
if !fieldKeyPattern.MatchString(input.FieldKey) {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "字段标识必须以字母开头,且只能包含字母、数字和下划线")
|
||||
return
|
||||
}
|
||||
item := model.ScenarioField{TenantID: p.TenantID, ScenarioID: scenarioID, FieldKey: input.FieldKey, FieldName: input.FieldName, FieldType: input.FieldType, Required: input.Required, Options: normalizedJSON(input.Options, `[]`), Validation: normalizedJSON(input.Validation, `{}`), SortOrder: input.SortOrder}
|
||||
if err := h.db.Create(&item).Error; err != nil {
|
||||
response.Error(c, http.StatusConflict, "CREATE_FAILED", "字段标识已存在或配置不正确")
|
||||
return
|
||||
}
|
||||
_ = audit.Record(h.db, p, "create", "scenario_field", item.ID, input)
|
||||
response.Created(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) UpdateField(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
id, ok := idParam(c, "fieldId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
var input fieldInput
|
||||
if err := c.ShouldBindJSON(&input); err != nil {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "字段配置不正确")
|
||||
return
|
||||
}
|
||||
if !fieldKeyPattern.MatchString(input.FieldKey) {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ARGUMENT", "字段标识必须以字母开头,且只能包含字母、数字和下划线")
|
||||
return
|
||||
}
|
||||
updates := map[string]interface{}{"field_key": input.FieldKey, "field_name": input.FieldName, "field_type": input.FieldType, "required": input.Required, "options": normalizedJSON(input.Options, `[]`), "validation": normalizedJSON(input.Validation, `{}`), "sort_order": input.SortOrder}
|
||||
result := h.db.Model(&model.ScenarioField{}).Where("id = ? AND tenant_id = ?", id, p.TenantID).Updates(updates)
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
response.Error(c, http.StatusNotFound, "NOT_FOUND", "字段不存在")
|
||||
return
|
||||
}
|
||||
_ = audit.Record(h.db, p, "update", "scenario_field", id, input)
|
||||
var item model.ScenarioField
|
||||
h.db.Where("id = ? AND tenant_id = ?", id, p.TenantID).First(&item)
|
||||
response.OK(c, item)
|
||||
}
|
||||
|
||||
func (h *Handler) DeleteField(c *gin.Context) {
|
||||
p, _ := auth.PrincipalFromContext(c)
|
||||
id, ok := idParam(c, "fieldId")
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
result := h.db.Where("id = ? AND tenant_id = ?", id, p.TenantID).Delete(&model.ScenarioField{})
|
||||
if result.Error != nil || result.RowsAffected == 0 {
|
||||
response.Error(c, http.StatusNotFound, "NOT_FOUND", "字段不存在")
|
||||
return
|
||||
}
|
||||
_ = audit.Record(h.db, p, "delete", "scenario_field", id, nil)
|
||||
response.OK(c, gin.H{"id": id})
|
||||
}
|
||||
|
||||
func (h *Handler) scenarioExists(tenantID, id uint64) bool {
|
||||
var count int64
|
||||
h.db.Model(&model.Scenario{}).Where("id = ? AND tenant_id = ? AND status <> ?", id, tenantID, "archived").Count(&count)
|
||||
return count == 1
|
||||
}
|
||||
|
||||
func normalizedJSON(value json.RawMessage, fallback string) datatypes.JSON {
|
||||
if len(value) == 0 || !json.Valid(value) {
|
||||
return datatypes.JSON([]byte(fallback))
|
||||
}
|
||||
return datatypes.JSON(value)
|
||||
}
|
||||
|
||||
func idParam(c *gin.Context, key string) (uint64, bool) {
|
||||
id, err := strconv.ParseUint(c.Param(key), 10, 64)
|
||||
if err != nil || id == 0 {
|
||||
response.Error(c, http.StatusBadRequest, "INVALID_ID", "资源 ID 不正确")
|
||||
return 0, false
|
||||
}
|
||||
return id, true
|
||||
}
|
||||
|
||||
func notFound(c *gin.Context, err error, message string) {
|
||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||
response.Error(c, http.StatusNotFound, "NOT_FOUND", message)
|
||||
return
|
||||
}
|
||||
response.Error(c, http.StatusInternalServerError, "QUERY_FAILED", "查询失败")
|
||||
}
|
||||
Reference in New Issue
Block a user