140 lines
3.8 KiB
Go
140 lines
3.8 KiB
Go
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 操作失败,请稍后重试")
|
|
}
|
|
}
|