picoclaw/web/backend/api/cron.go

152 lines
No EOL
5.2 KiB
Go

package api
import (
"encoding/json"
"fmt"
"net/http"
"github.com/sipeed/picoclaw/pkg/config"
"github.com/sipeed/picoclaw/pkg/cron"
)
// registerCronRoutes registers cron job API routes on the ServeMux.
func (h *Handler) registerCronRoutes(mux *http.ServeMux) {
mux.HandleFunc("GET /api/cron/jobs", h.handleListCronJobs)
mux.HandleFunc("POST /api/cron/jobs", h.handleAddCronJob)
mux.HandleFunc("DELETE /api/cron/jobs/{id}", h.handleDeleteCronJob)
mux.HandleFunc("POST /api/cron/jobs/{id}/enable", h.handleEnableCronJob)
mux.HandleFunc("POST /api/cron/jobs/{id}/disable", h.handleDisableCronJob)
}
type cronJobResponse struct {
Jobs []cron.CronJob `json:"jobs"`
}
type cronAddRequest struct {
Name string `json:"name" binding:"required"`
EveryMS *int64 `json:"every_ms,omitempty"`
CronExpr string `json:"cron_expr,omitempty"`
Message string `json:"message" binding:"required"`
Channel string `json:"channel,omitempty"`
To string `json:"to,omitempty"`
}
// handleListCronJobs lists all cron jobs from the cron service.
func (h *Handler) handleListCronJobs(w http.ResponseWriter, r *http.Request) {
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
storePath := cfg.WorkspacePath() + "/cron/jobs.json"
cs := cron.NewCronService(storePath, nil)
if err := cs.Load(); err != nil {
http.Error(w, fmt.Sprintf("Failed to load cron store: %v", err), http.StatusInternalServerError)
return
}
jobs := cs.ListJobs(true)
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(cronJobResponse{Jobs: jobs})
}
// handleAddCronJob adds a new cron job.
func (h *Handler) handleAddCronJob(w http.ResponseWriter, r *http.Request) {
var req cronAddRequest
if err := json.NewDecoder(r.Body).Decode(&req); err != nil {
http.Error(w, fmt.Sprintf("Invalid JSON: %v", err), http.StatusBadRequest)
return
}
if req.Name == "" || req.Message == "" {
http.Error(w, "name and message are required", http.StatusBadRequest)
return
}
var schedule cron.CronSchedule
if req.EveryMS != nil {
schedule = cron.CronSchedule{Kind: "every", EveryMS: req.EveryMS}
} else if req.CronExpr != "" {
schedule = cron.CronSchedule{Kind: "cron", Expr: req.CronExpr}
} else {
http.Error(w, "either every_ms or cron_expr must be specified", http.StatusBadRequest)
return
}
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
storePath := cfg.WorkspacePath() + "/cron/jobs.json"
cs := cron.NewCronService(storePath, nil)
job, err := cs.AddJob(req.Name, schedule, req.Message, req.Channel, req.To)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to add job: %v", err), http.StatusInternalServerError)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]any{"job": job, "status": "ok"})
}
// handleDeleteCronJob deletes a cron job by ID.
func (h *Handler) handleDeleteCronJob(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
http.Error(w, "job id is required", http.StatusBadRequest)
return
}
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
storePath := cfg.WorkspacePath() + "/cron/jobs.json"
cs := cron.NewCronService(storePath, nil)
if removed := cs.RemoveJob(id); !removed {
http.Error(w, "job not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
// handleEnableCronJob enables a cron job by ID.
func (h *Handler) handleEnableCronJob(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
http.Error(w, "job id is required", http.StatusBadRequest)
return
}
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
storePath := cfg.WorkspacePath() + "/cron/jobs.json"
cs := cron.NewCronService(storePath, nil)
if job := cs.EnableJob(id, true); job == nil {
http.Error(w, "job not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}
// handleDisableCronJob disables a cron job by ID.
func (h *Handler) handleDisableCronJob(w http.ResponseWriter, r *http.Request) {
id := r.PathValue("id")
if id == "" {
http.Error(w, "job id is required", http.StatusBadRequest)
return
}
cfg, err := config.LoadConfig(h.configPath)
if err != nil {
http.Error(w, fmt.Sprintf("Failed to load config: %v", err), http.StatusInternalServerError)
return
}
storePath := cfg.WorkspacePath() + "/cron/jobs.json"
cs := cron.NewCronService(storePath, nil)
if job := cs.EnableJob(id, false); job == nil {
http.Error(w, "job not found", http.StatusNotFound)
return
}
w.Header().Set("Content-Type", "application/json")
json.NewEncoder(w).Encode(map[string]string{"status": "ok"})
}