refactor(backend): split project write responsibilities

This commit is contained in:
2026-07-21 15:27:11 +08:00
parent 9e862f2a7f
commit 1fcbb31301
18 changed files with 1025 additions and 649 deletions

View File

@@ -0,0 +1,98 @@
package projects
import (
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"senlinai-agent/backend/internal/httpx"
"senlinai-agent/backend/internal/models"
)
// CronPlanDTO 是计划任务管理响应MVP 只管理提醒元数据,不执行自主 Agent。
type CronPlanDTO struct {
ID string `json:"id"`
ProjectID string `json:"projectId"`
Title string `json:"title"`
Schedule string `json:"schedule"`
NextRunAt *time.Time `json:"nextRunAt"`
Enabled bool `json:"enabled"`
LastResult string `json:"lastResult"`
CreatedAt time.Time `json:"createdAt"`
UpdatedAt time.Time `json:"updatedAt"`
}
// CronHandler 单独承载计划任务写接口。
type CronHandler struct {
service *Service
}
// NewCronHandler 创建计划任务 HTTP registrar。
func NewCronHandler(service *Service) *CronHandler {
return &CronHandler{service: service}
}
// Register 注册项目内计划任务创建接口。
func (h *CronHandler) Register(router gin.IRouter) {
router.POST("/projects/:id/cron-plans", h.create)
}
func (h *CronHandler) create(c *gin.Context) {
userID, project, ok := ownedProjectFromRequest(c)
if !ok {
return
}
var input struct {
Title string `json:"title"`
Schedule string `json:"schedule"`
Enabled bool `json:"enabled"`
NextRunAt string `json:"nextRunAt"`
}
if err := c.ShouldBindJSON(&input); err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "请求参数无效")
return
}
nextRunAt, err := parseCronOptionalTime(input.NextRunAt)
if err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "下次运行时间格式无效")
return
}
plan, err := h.service.CreateCronPlan(userID, project.ID, CreateCronPlanInput{
Title: input.Title, Schedule: input.Schedule, Enabled: input.Enabled, NextRunAt: nextRunAt,
})
if err != nil {
httpx.Error(c, http.StatusBadRequest, "invalid_request", "计划任务参数无效")
return
}
c.JSON(http.StatusCreated, cronPlanDTO(*plan))
}
func cronPlanDTO(plan models.SenlinAgentCronPlan) CronPlanDTO {
return CronPlanDTO{
ID: plan.Identity, ProjectID: plan.ProjectIdentity, Title: plan.Title, Schedule: plan.Schedule,
NextRunAt: projectUTCOptionalTime(plan.NextRunAt), Enabled: plan.Enabled, LastResult: plan.LastResult,
CreatedAt: plan.CreatedAt.UTC(), UpdatedAt: plan.UpdatedAt.UTC(),
}
}
func parseCronOptionalTime(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 projectUTCOptionalTime(value *time.Time) *time.Time {
if value == nil {
return nil
}
result := value.UTC()
return &result
}