* H-289: add Captain Skill management API * H-289: guard Captain Skill updates with CAS * H-289: version all Captain Skill updates --------- Co-authored-by: Rogee <rogee@ipao.vip>
194 lines
6.0 KiB
Go
194 lines
6.0 KiB
Go
package v1
|
|
|
|
import (
|
|
"errors"
|
|
"net/http"
|
|
"strconv"
|
|
|
|
"github.com/gin-gonic/gin"
|
|
"github.com/gochat/gochat/internal/model"
|
|
"github.com/gochat/gochat/internal/service"
|
|
"github.com/gochat/gochat/pkg/response"
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
type CaptainSkillHandler struct{ svc *service.CaptainSkillService }
|
|
|
|
func NewCaptainSkillHandler(svc *service.CaptainSkillService) *CaptainSkillHandler {
|
|
return &CaptainSkillHandler{svc: svc}
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) List(c *gin.Context) {
|
|
accountID := parseAccountIDParam(c)
|
|
if accountID == 0 {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
|
|
return
|
|
}
|
|
var assistantID *uint
|
|
if raw := c.Query("assistant_id"); raw != "" {
|
|
value, err := strconv.ParseUint(raw, 10, 32)
|
|
if err != nil || value == 0 {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid assistant_id")
|
|
return
|
|
}
|
|
id := uint(value)
|
|
assistantID = &id
|
|
}
|
|
items, err := h.svc.List(c.Request.Context(), accountID, assistantID)
|
|
if err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
payload := make([]gin.H, len(items))
|
|
for i := range items {
|
|
payload[i] = captainSkillSummaryPayload(items[i])
|
|
}
|
|
c.JSON(http.StatusOK, gin.H{"payload": payload, "meta": gin.H{"total_count": len(payload)}})
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) Create(c *gin.Context) {
|
|
accountID := parseAccountIDParam(c)
|
|
if accountID == 0 {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id")
|
|
return
|
|
}
|
|
var req service.CaptainSkillRequest
|
|
if err := bindNestedJSONPayload(c, "skill", &req); err != nil {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
|
return
|
|
}
|
|
skill, err := h.svc.Create(c.Request.Context(), accountID, &req)
|
|
if err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, captainSkillDetailPayload(skill))
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) Get(c *gin.Context) {
|
|
accountID, skillID, ok := captainSkillIDs(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
skill, err := h.svc.Get(c.Request.Context(), accountID, skillID)
|
|
if err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, captainSkillDetailPayload(skill))
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) Update(c *gin.Context) {
|
|
accountID, skillID, ok := captainSkillIDs(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
var req service.CaptainSkillRequest
|
|
if err := bindNestedJSONPayload(c, "skill", &req); err != nil {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrValidation, err.Error())
|
|
return
|
|
}
|
|
skill, err := h.svc.Update(c.Request.Context(), accountID, skillID, &req)
|
|
if err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
c.JSON(http.StatusOK, captainSkillDetailPayload(skill))
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) Delete(c *gin.Context) {
|
|
accountID, skillID, ok := captainSkillIDs(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.svc.Delete(c.Request.Context(), accountID, skillID); err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
response.NoContent(c)
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) Bind(c *gin.Context) {
|
|
accountID, assistantID, skillID, ok := captainAssistantSkillIDs(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.svc.Bind(c.Request.Context(), accountID, assistantID, skillID); err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
response.NoContent(c)
|
|
}
|
|
|
|
func (h *CaptainSkillHandler) Unbind(c *gin.Context) {
|
|
accountID, assistantID, skillID, ok := captainAssistantSkillIDs(c)
|
|
if !ok {
|
|
return
|
|
}
|
|
if err := h.svc.Unbind(c.Request.Context(), accountID, assistantID, skillID); err != nil {
|
|
renderCaptainSkillError(c, err)
|
|
return
|
|
}
|
|
response.NoContent(c)
|
|
}
|
|
|
|
func captainSkillIDs(c *gin.Context) (uint, uint, bool) {
|
|
accountID := parseAccountIDParam(c)
|
|
skillID, err := parseUintAnyParam(c, "skill_id")
|
|
if accountID == 0 || err != nil || skillID == 0 {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid account_id or skill_id")
|
|
return 0, 0, false
|
|
}
|
|
return accountID, skillID, true
|
|
}
|
|
|
|
func captainAssistantSkillIDs(c *gin.Context) (uint, uint, uint, bool) {
|
|
accountID, skillID, ok := captainSkillIDs(c)
|
|
if !ok {
|
|
return 0, 0, 0, false
|
|
}
|
|
assistantID, err := parseUintAnyParam(c, "assistant_id")
|
|
if err != nil || assistantID == 0 {
|
|
response.AbortWithStatusError(c, http.StatusBadRequest, response.ErrBadRequest, "invalid assistant_id")
|
|
return 0, 0, 0, false
|
|
}
|
|
return accountID, assistantID, skillID, true
|
|
}
|
|
|
|
func renderCaptainSkillError(c *gin.Context, err error) {
|
|
switch {
|
|
case errors.Is(err, gorm.ErrRecordNotFound):
|
|
response.AbortWithStatusError(c, http.StatusNotFound, response.ErrNotFound, "skill or assistant not found")
|
|
case errors.Is(err, service.ErrCaptainSkillValidation):
|
|
c.JSON(http.StatusUnprocessableEntity, gin.H{"error": err.Error()})
|
|
case errors.Is(err, service.ErrCaptainSkillConflict):
|
|
c.JSON(http.StatusConflict, gin.H{"error": err.Error()})
|
|
default:
|
|
response.AbortWithStatusError(c, http.StatusInternalServerError, response.ErrInternal, "failed to manage captain skill")
|
|
}
|
|
}
|
|
|
|
func captainSkillSummaryPayload(item service.CaptainSkillListItem) gin.H {
|
|
skill := item.Skill
|
|
return gin.H{
|
|
"id": skill.ID, "name": skill.Name, "description": skill.Description, "status": skill.Status,
|
|
"version": skill.Version, "reference_count": item.ReferenceCount, "bound": item.Bound,
|
|
"bound_assistant_count": item.BoundAssistantCount, "updated_at": skill.UpdatedAt.Unix(),
|
|
}
|
|
}
|
|
|
|
func captainSkillDetailPayload(skill *model.CaptainSkill) gin.H {
|
|
references := make([]gin.H, len(skill.References))
|
|
for i := range skill.References {
|
|
reference := skill.References[i]
|
|
references[i] = gin.H{
|
|
"id": reference.ID, "reference_key": reference.ReferenceKey, "content_md": reference.ContentMD, "position": reference.Position,
|
|
}
|
|
}
|
|
return gin.H{
|
|
"id": skill.ID, "name": skill.Name, "description": skill.Description, "instructions_md": skill.InstructionsMD,
|
|
"status": skill.Status, "version": skill.Version, "references": references,
|
|
"created_at": skill.CreatedAt.Unix(), "updated_at": skill.UpdatedAt.Unix(),
|
|
}
|
|
}
|