Files
zeekay 46cf0bae05 refactor(cron): route every scheduled-job path through tools/tasks
tools/cron is now a thin alias over tools/tasks. The legacy ticker +
crontab-matching implementation is deleted; the public surface (New,
Add/MustAdd, Remove, RemoveAll, Jobs, HasJob, Total, Start, Stop,
SetInterval, SetTimezone, HasStarted) is preserved so no callsite
changes. app.Cron() and app.Tasks() share a single *tasks.Client, giving
one obvious way to schedule work.

- tools/tasks/client.go gains the primitives the cron shim needs
  (Remove, RemoveAll, HasJob, Total, Schedules, Run, PauseAll, ResumeAll,
  ActiveTickerCount) plus a richer internal scheduleEntry so remote vs
  local ticking are distinguishable.
- cron.Cron.Stop() now pauses local tickers while preserving the
  registry, matching the legacy semantics expected by ResetBootstrapState
  and the admin API.
- core/base.go builds app.cron via cron.NewFromTasks(app.tasks) so both
  accessors feed the same state.
- apis/cron.go keeps the /crons list + run-now behaviour; lookup now
  uses HasJob() for an O(1) precheck.

Tests rewritten around the shim (TestCronDelegatesToTasks,
TestCronAppFacadeLocal, TestCronAppFacadeRemoteFallback,
TestCronNewFromTasksShared). Legacy 1-minute-tick assertions
(TestCronStartStop) are SKIPped with a pointer to their replacement;
crontab parser tests (tools/cron/schedule_test.go) still pass — the
parser stays as a validation primitive for settings_model.go.

docs/cron-to-tasks.md covers the migration path for services.
2026-04-22 15:14:45 -07:00

61 lines
1.3 KiB
Go

package apis
import (
"net/http"
"slices"
"strings"
"github.com/hanzoai/base/core"
"github.com/hanzoai/base/tools/cron"
"github.com/hanzoai/base/tools/router"
"github.com/hanzoai/base/tools/routine"
)
// bindCronApi registers the crons api endpoint.
func bindCronApi(app core.App, rg *router.RouterGroup[*core.RequestEvent]) {
subGroup := rg.Group("/crons").Bind(RequireSuperuserAuth())
subGroup.GET("", cronsList)
subGroup.POST("/{id}", cronRun)
}
func cronsList(e *core.RequestEvent) error {
jobs := e.App.Cron().Jobs()
slices.SortStableFunc(jobs, func(a, b *cron.Job) int {
if strings.HasPrefix(a.Id(), "__hz") {
return 1
}
if strings.HasPrefix(b.Id(), "__hz") {
return -1
}
return strings.Compare(a.Id(), b.Id())
})
return e.JSON(http.StatusOK, jobs)
}
func cronRun(e *core.RequestEvent) error {
cronId := e.Request.PathValue("id")
if !e.App.Cron().HasJob(cronId) {
return e.NotFoundError("Missing or invalid cron job", nil)
}
var foundJob *cron.Job
for _, j := range e.App.Cron().Jobs() {
if j.Id() == cronId {
foundJob = j
break
}
}
if foundJob == nil {
return e.NotFoundError("Missing or invalid cron job", nil)
}
routine.FireAndForget(func() {
foundJob.Run()
})
return e.NoContent(http.StatusNoContent)
}