refactor(backend): split project write responsibilities
This commit is contained in:
153
backend/internal/logic/tasks/handlers.go
Normal file
153
backend/internal/logic/tasks/handlers.go
Normal file
@@ -0,0 +1,153 @@
|
||||
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
|
||||
}
|
||||
if strings.TrimSpace(input.NextProjectID) != "" {
|
||||
if _, ok := parseIdentity(input.NextProjectID); !ok {
|
||||
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: input.NextProjectID, 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", "任务操作失败")
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user