Files
agent/backend/internal/logic/tasks/handlers.go

157 lines
4.1 KiB
Go

package tasks
import (
"errors"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"github.com/google/uuid"
"gorm.io/gorm"
"senlinai-agent/backend/internal/httpx"
"senlinai-agent/backend/internal/logic/auth"
)
// Handler 只注册任务写接口,项目核心 handler 不再承担任务职责。
type Handler struct {
service *Service
}
// NewHandler 创建任务 HTTP registrar。
func NewHandler(service *Service) *Handler {
return &Handler{service: service}
}
// Register 将任务写接口注册到上层提供的 /api/v1 路由组。
func (h *Handler) Register(router gin.IRouter) {
router.POST("/projects/:id/tasks", h.create)
router.PATCH("/projects/:id/tasks/:taskId", h.update)
}
type createTaskRequest struct {
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
DueAt string `json:"dueAt"`
Tag string `json:"tag"`
}
type updateTaskRequest struct {
Title string `json:"title"`
Description string `json:"description"`
Status string `json:"status"`
Completed bool `json:"completed"`
NextProjectID string `json:"nextProjectId"`
Tag string `json:"tag"`
}
func (h *Handler) create(c *gin.Context) {
userID, ok := currentUser(c)
if !ok {
return
}
projectIdentity, ok := httpx.IdentityParam(c, "id")
if !ok {
return
}
var input createTaskRequest
if err := c.ShouldBindJSON(&input); err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
return
}
dueAt, err := parseOptionalTime(input.DueAt)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "截止时间格式无效")
return
}
task, err := h.service.Create(userID, projectIdentity, CreateTaskInput{
Title: input.Title, Description: input.Description, Status: input.Status, DueAt: dueAt, Tag: input.Tag,
})
if err != nil {
writeTaskError(c, err)
return
}
c.JSON(http.StatusCreated, task)
}
func (h *Handler) update(c *gin.Context) {
userID, ok := currentUser(c)
if !ok {
return
}
projectIdentity, ok := httpx.IdentityParam(c, "id")
if !ok {
return
}
taskIdentity, ok := httpx.IdentityParam(c, "taskId")
if !ok {
return
}
var input updateTaskRequest
if err := c.ShouldBindJSON(&input); err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
return
}
nextProjectIdentity := ""
if strings.TrimSpace(input.NextProjectID) != "" {
var valid bool
nextProjectIdentity, valid = parseIdentity(input.NextProjectID)
if !valid {
httpx.Error(c, http.StatusBadRequest, "invalid_identity", "nextProjectId 必须是 UUIDv7")
return
}
}
task, err := h.service.Update(userID, projectIdentity, taskIdentity, UpdateTaskInput{
Title: input.Title, Description: input.Description, Status: input.Status, Completed: input.Completed,
NextProjectIdentity: nextProjectIdentity, Tag: input.Tag,
})
if err != nil {
writeTaskError(c, err)
return
}
c.JSON(http.StatusOK, task)
}
func currentUser(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 parseOptionalTime(value string) (*time.Time, error) {
trimmed := strings.TrimSpace(value)
if trimmed == "" {
return nil, nil
}
parsed, err := time.Parse(time.RFC3339, trimmed)
if err != nil {
return nil, err
}
parsed = parsed.UTC()
return &parsed, nil
}
func parseIdentity(value string) (string, bool) {
trimmed := strings.TrimSpace(value)
identity, err := uuid.Parse(trimmed)
if err != nil || identity.Version() != 7 || identity.Variant() != uuid.RFC4122 {
return "", false
}
return identity.String(), true
}
func writeTaskError(c *gin.Context, err error) {
switch {
case errors.Is(err, gorm.ErrRecordNotFound):
httpx.Error(c, http.StatusNotFound, "not_found", "任务或项目不存在")
case errors.Is(err, ErrTaskTitleRequired):
httpx.Error(c, http.StatusBadRequest, "invalid_request", "任务标题不能为空")
default:
httpx.Error(c, http.StatusInternalServerError, "internal_error", "任务操作失败")
}
}