Files
gochat/backend/internal/handler/api/v1/captain_skill_handler.go
T
Rogeeandrogee 0f2b8d7857 H-289: add Captain Skill management API (#46)
* 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>
2026-08-18 14:29:10 +08:00

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(),
}
}