From b25d7be29d01322c62fd68924063ce9a0e8f3984 Mon Sep 17 00:00:00 2001 From: Ruidy Date: Sun, 30 Jun 2024 12:49:26 +0200 Subject: [PATCH] basic task scheduler --- Makefile | 2 + cmd/cron/main.go | 48 ++++++++++++++++++++ internal/cron/cron.go | 88 +++++++++++++++++++++++++++++++++++++ internal/cron/job_report.go | 16 +++++++ 4 files changed, 154 insertions(+) create mode 100644 cmd/cron/main.go create mode 100644 internal/cron/cron.go create mode 100644 internal/cron/job_report.go diff --git a/Makefile b/Makefile index 86eeb8a..a865ab0 100644 --- a/Makefile +++ b/Makefile @@ -9,6 +9,8 @@ run: build @docker run -p ${PORT}:${PORT} -e DATABASE_URL="host=docker.for.mac.host.internal user=${DB_USER} database=${DB_NAME}" -e PORT=${PORT} ${NAME} dev: templ @air cmd/main.go +cron: + @go run cmd/cron/main.go templ: @templ generate --watch --proxy=http://localhost:${PORT} & format: diff --git a/cmd/cron/main.go b/cmd/cron/main.go new file mode 100644 index 0000000..137598d --- /dev/null +++ b/cmd/cron/main.go @@ -0,0 +1,48 @@ +package main + +import ( + "log" + "os" + "os/signal" + "syscall" + + "github.com/rjNemo/rentease/internal/cron" +) + +func main() { + scheduler := cron.New() + + scheduler.AddJob(cron.Job{ + Name: "Monthly Booking Report", + Schedule: "minute", + //Schedule: "monthly", + Action: cron.JobMonthlyBookingReport, + }) + + go scheduler.Start() + + go func() { + for { + select { + case err := <-scheduler.ErrChan: + if err != nil { + log.Println("Error:", err) + } + case msg := <-scheduler.SuccessChan: + log.Print(msg) + } + } + }() + + // Capture termination signals for graceful shutdown + sigChan := make(chan os.Signal, 1) + signal.Notify(sigChan, syscall.SIGINT, syscall.SIGTERM) + + // Wait for termination signal + <-sigChan + log.Println("Received termination signal, shutting down...") + + // Stop the task manager and close channels + scheduler.Stop() + log.Println("All tasks stopped, exiting.") +} diff --git a/internal/cron/cron.go b/internal/cron/cron.go new file mode 100644 index 0000000..637276f --- /dev/null +++ b/internal/cron/cron.go @@ -0,0 +1,88 @@ +package cron + +import ( + "fmt" + "log" + "time" +) + +// Cron handles jobs scheduling and execution +type Cron struct { + jobs []Job + ErrChan chan error + DoneChan chan struct{} + SuccessChan chan string +} + +// Job is a type that holds the details for each job. +type Job struct { + Name string + Schedule string + Action JobFunc +} + +type JobFunc func() error + +func New() *Cron { + return &Cron{ + jobs: make([]Job, 0), + ErrChan: make(chan error), + SuccessChan: make(chan string), + DoneChan: make(chan struct{}), + } +} + +func (c *Cron) AddJob(job Job) { + c.jobs = append(c.jobs, job) +} + +func (c *Cron) Start() { + for _, j := range c.jobs { + go c.scheduleJob(j) + } +} + +func (c *Cron) Stop() { + close(c.DoneChan) + close(c.SuccessChan) + close(c.ErrChan) +} + +// scheduleJob adds a task to the Cron schedule based on its schedule +func (c *Cron) scheduleJob(j Job) { + for { + select { + case <-c.DoneChan: + log.Printf("stopping job %s", j.Name) + return + default: + now := time.Now() + + var next time.Time + switch j.Schedule { + case "minute": + next = now.Add(10 * time.Second) + case "daily": + next = now.AddDate(0, 0, 1).Truncate(24 * time.Hour) + case "weekly": + next = now.AddDate(0, 0, 7).Truncate(24 * time.Hour) + case "monthly": + nextMonth := now.AddDate(0, 1, 0) + next = time.Date(nextMonth.Year(), nextMonth.Month(), 1, 0, 0, 0, 0, nextMonth.Location()) + default: + log.Printf("Unknown schedule %q for job %q", j.Schedule, j.Name) + return + } + + sleepDuration := time.Until(next) + log.Printf("Job %q will run in %s", j.Name, sleepDuration.String()) + time.Sleep(sleepDuration) + + if err := j.Action(); err != nil { + c.ErrChan <- fmt.Errorf("job %s failed: %w", j.Name, err) + } else { + c.SuccessChan <- fmt.Sprintf("job %s completed successfully", j.Name) + } + } + } +} diff --git a/internal/cron/job_report.go b/internal/cron/job_report.go new file mode 100644 index 0000000..c368ad1 --- /dev/null +++ b/internal/cron/job_report.go @@ -0,0 +1,16 @@ +package cron + +import ( + "errors" + "log" + "time" +) + +func JobMonthlyBookingReport() error { + now := time.Now() + log.Println("Start Monthly Booking Report job at:", now) + //err := booking.NewService().BuildReport("monthly", int(now.Month()), now.Year()) + err := errors.New("test") + log.Printf("Executed Monthly Booking Report job at %v with errors: %s:", time.Now().Format(time.DateTime), err) + return err +}