package inbox import ( "errors" "net/http" "time" "github.com/gin-gonic/gin" "gorm.io/gorm" "senlinai-agent/backend/internal/httpx" "senlinai-agent/backend/internal/logic/auth" "senlinai-agent/backend/internal/models" ) type Handler struct { service *Service } // InboxItemDTO 隔离 Gorm 自增主键,只通过公开 identity 关联项目和条目。 type InboxItemDTO struct { ID string `json:"id"` ProjectID string `json:"projectId"` SourceType string `json:"sourceType"` Title string `json:"title"` Body string `json:"body"` Status string `json:"status"` CreatedAt time.Time `json:"createdAt"` UpdatedAt time.Time `json:"updatedAt"` } func NewHandler(service *Service) *Handler { return &Handler{service: service} } func (h *Handler) Register(router gin.IRouter) { router.POST("/projects/:id/inbox", h.capture) router.POST("/inbox/:id/analyze", h.analyze) router.POST("/inbox/:id/confirm", h.confirm) } func (h *Handler) capture(c *gin.Context) { userID, ok := currentInboxUser(c) if !ok { return } projectIdentity, ok := httpx.IdentityParam(c, "id") if !ok { return } var input struct { SourceType string `json:"sourceType"` Title string `json:"title"` Body string `json:"body"` } if err := c.ShouldBindJSON(&input); err != nil { httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效") return } item, err := h.service.Capture(CaptureInput{ ProjectIdentity: projectIdentity, UserID: userID, SourceType: input.SourceType, Title: input.Title, Body: input.Body, }) if err != nil { writeInboxError(c, err) return } c.JSON(http.StatusCreated, inboxItemDTO(*item)) } func (h *Handler) analyze(c *gin.Context) { userID, ok := currentInboxUser(c) if !ok { return } itemIdentity, ok := httpx.IdentityParam(c, "id") if !ok { return } suggestions, err := h.service.Analyze(itemIdentity, userID) if err != nil { writeInboxError(c, err) return } c.JSON(http.StatusOK, gin.H{"suggestions": suggestions}) } func (h *Handler) confirm(c *gin.Context) { userID, ok := currentInboxUser(c) if !ok { return } itemIdentity, ok := httpx.IdentityParam(c, "id") if !ok { return } var input struct { SuggestionIDs []string `json:"suggestionIds"` } if err := c.ShouldBindJSON(&input); err != nil { httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效") return } result, err := h.service.Confirm(itemIdentity, userID, input.SuggestionIDs) if err != nil { writeInboxError(c, err) return } c.JSON(http.StatusOK, result) } func currentInboxUser(c *gin.Context) (uint, bool) { userID, ok := auth.CurrentUserID(c) if !ok { httpx.Error(c, http.StatusUnauthorized, "unauthorized", "未登录或登录已失效") return 0, false } return userID, true } func inboxItemDTO(item models.SenlinAgentInboxItem) InboxItemDTO { return InboxItemDTO{ ID: item.Identity, ProjectID: item.ProjectIdentity, SourceType: item.SourceType, Title: item.Title, Body: item.Body, Status: item.Status, CreatedAt: item.CreatedAt.UTC(), UpdatedAt: item.UpdatedAt.UTC(), } } func writeInboxError(c *gin.Context, err error) { switch { case errors.Is(err, gorm.ErrRecordNotFound): httpx.Error(c, http.StatusNotFound, "not_found", "Inbox 条目或项目不存在") case errors.Is(err, ErrInboxAlreadyConfirmed): httpx.Error(c, http.StatusConflict, "conflict", "该 Inbox 条目已经确认处理") case errors.Is(err, ErrProjectAndUserRequired), errors.Is(err, ErrSourceTypeRequired), errors.Is(err, ErrSuggestionSelectionRequired), errors.Is(err, ErrSuggestionNotFound): httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数或建议选择无效") default: httpx.Error(c, http.StatusInternalServerError, "internal_error", "Inbox 操作失败,请稍后重试") } }