Files
agent/backend/internal/logic/projects/cron_handlers.go

112 lines
3.2 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package projects
import (
"errors"
"net/http"
"strings"
"time"
"github.com/gin-gonic/gin"
"gorm.io/gorm"
"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 {
writeCronError(c, err)
return
}
c.JSON(http.StatusCreated, cronPlanDTO(*plan))
}
func writeCronError(c *gin.Context, err error) {
switch {
case errors.Is(err, ErrCronTitleRequired), errors.Is(err, ErrCronScheduleRequired):
httpx.Error(c, http.StatusBadRequest, "invalid_request", "计划任务参数无效")
case errors.Is(err, gorm.ErrRecordNotFound):
httpx.Error(c, http.StatusNotFound, "not_found", "项目不存在")
default:
httpx.Error(c, http.StatusInternalServerError, "internal_error", "计划任务创建失败")
}
}
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
}