package handler import ( "fmt" "net/http" "strconv" "time" "spider/internal/model" "spider/internal/store" "spider/internal/task" "github.com/gin-gonic/gin" ) // ScheduleHandler handles schedule CRUD endpoints. type ScheduleHandler struct { store *store.Store scheduler *task.Scheduler taskMgr *task.Manager } // SetScheduler sets the scheduler reference (called after scheduler is created). func (h *ScheduleHandler) SetScheduler(s *task.Scheduler) { h.scheduler = s } // List handles GET /schedules func (h *ScheduleHandler) List(c *gin.Context) { var jobs []model.ScheduleJob if err := h.store.DB.Order("id ASC").Find(&jobs).Error; err != nil { Fail(c, 500, err.Error()) return } OK(c, jobs) } // Create handles POST /schedules func (h *ScheduleHandler) Create(c *gin.Context) { var req struct { Name string `json:"name" binding:"required"` PluginName string `json:"plugin_name" binding:"required"` CronExpr string `json:"cron_expr" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, 400, err.Error()) return } job := model.ScheduleJob{ Name: req.Name, PluginName: req.PluginName, CronExpr: req.CronExpr, Enabled: true, } if err := h.store.DB.Create(&job).Error; err != nil { Fail(c, 500, err.Error()) return } if h.scheduler != nil { h.scheduler.Reload() } LogAudit(h.store, c, "create", "schedule", fmt.Sprintf("%d", job.ID), gin.H{"name": job.Name, "plugin": job.PluginName}) OK(c, job) } // Update handles PUT /schedules/:id func (h *ScheduleHandler) Update(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } var job model.ScheduleJob if err := h.store.DB.First(&job, id).Error; err != nil { Fail(c, 404, "定时任务不存在") return } var req struct { Name *string `json:"name"` CronExpr *string `json:"cron_expr"` Enabled *bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { Fail(c, 400, err.Error()) return } updates := map[string]any{} if req.Name != nil { updates["name"] = *req.Name } if req.CronExpr != nil { updates["cron_expr"] = *req.CronExpr } if req.Enabled != nil { updates["enabled"] = *req.Enabled } if len(updates) > 0 { if err := h.store.DB.Model(&job).Updates(updates).Error; err != nil { Fail(c, 500, err.Error()) return } } // Re-read h.store.DB.First(&job, id) if h.scheduler != nil { h.scheduler.Reload() } LogAudit(h.store, c, "update", "schedule", fmt.Sprintf("%d", id), updates) OK(c, job) } // Delete handles DELETE /schedules/:id func (h *ScheduleHandler) Delete(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } if err := h.store.DB.Delete(&model.ScheduleJob{}, id).Error; err != nil { Fail(c, 500, err.Error()) return } if h.scheduler != nil { h.scheduler.Reload() } LogAudit(h.store, c, "delete", "schedule", fmt.Sprintf("%d", id), nil) OK(c, nil) } // RunNow handles POST /schedules/:id/run — manually triggers a scheduled job immediately func (h *ScheduleHandler) RunNow(c *gin.Context) { id, err := strconv.ParseUint(c.Param("id"), 10, 64) if err != nil { Fail(c, 400, "invalid id") return } var job model.ScheduleJob if err := h.store.DB.First(&job, id).Error; err != nil { Fail(c, 404, "定时任务不存在") return } // Start the task using the task manager via scheduler req := task.StartRequest{PluginName: job.PluginName} taskLog, err := h.taskMgr.StartTask(req) if err != nil { Fail(c, 409, err.Error()) return } // Update last_run_at now := time.Now() h.store.DB.Model(&job).Update("last_run_at", now) LogAudit(h.store, c, "create", "task", fmt.Sprintf("%d", taskLog.ID), gin.H{"trigger": "manual_schedule", "schedule_id": id}) c.JSON(http.StatusCreated, Response{Code: 0, Message: "ok", Data: taskLog}) }