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