first commit

Made-with: Cursor
This commit is contained in:
Michilis
2026-02-28 02:17:55 +00:00
commit 41f6ae916f
92 changed files with 12332 additions and 0 deletions

View File

@@ -0,0 +1,63 @@
package scheduler
import (
"context"
"encoding/json"
"fmt"
"time"
"github.com/google/uuid"
"github.com/hibiken/asynq"
)
const TypeReminder = "reminder:send"
type ReminderPayload struct {
EventID uuid.UUID `json:"event_id"`
ReminderID uuid.UUID `json:"reminder_id"`
UserID uuid.UUID `json:"user_id"`
}
type Scheduler struct {
client *asynq.Client
}
func NewScheduler(redisAddr string) *Scheduler {
client := asynq.NewClient(asynq.RedisClientOpt{Addr: redisAddr})
return &Scheduler{client: client}
}
func (s *Scheduler) ScheduleReminder(_ context.Context, eventID, reminderID, userID uuid.UUID, triggerAt time.Time) error {
payload, err := json.Marshal(ReminderPayload{
EventID: eventID,
ReminderID: reminderID,
UserID: userID,
})
if err != nil {
return fmt.Errorf("marshal reminder payload: %w", err)
}
task := asynq.NewTask(TypeReminder, payload)
_, err = s.client.Enqueue(task,
asynq.ProcessAt(triggerAt),
asynq.MaxRetry(5),
asynq.Queue("reminders"),
)
if err != nil {
return fmt.Errorf("enqueue reminder: %w", err)
}
return nil
}
func (s *Scheduler) Close() error {
return s.client.Close()
}
type NoopScheduler struct{}
func (NoopScheduler) ScheduleReminder(_ context.Context, _, _, _ uuid.UUID, _ time.Time) error {
return nil
}
func (NoopScheduler) Close() error { return nil }

View File

@@ -0,0 +1,67 @@
package scheduler
import (
"context"
"encoding/json"
"fmt"
"log"
"github.com/calendarapi/internal/repository"
"github.com/calendarapi/internal/utils"
"github.com/hibiken/asynq"
)
type ReminderWorker struct {
queries *repository.Queries
}
func NewReminderWorker(queries *repository.Queries) *ReminderWorker {
return &ReminderWorker{queries: queries}
}
func (w *ReminderWorker) HandleReminderTask(ctx context.Context, t *asynq.Task) error {
var payload ReminderPayload
if err := json.Unmarshal(t.Payload(), &payload); err != nil {
return fmt.Errorf("unmarshal payload: %w", err)
}
ev, err := w.queries.GetEventByID(ctx, utils.ToPgUUID(payload.EventID))
if err != nil {
return fmt.Errorf("get event: %w", err)
}
if ev.DeletedAt.Valid {
log.Printf("reminder skipped: event %s deleted", payload.EventID)
return nil
}
log.Printf("reminder triggered: event=%s user=%s title=%s",
payload.EventID, payload.UserID, ev.Title)
return nil
}
func StartWorker(redisAddr string, worker *ReminderWorker) *asynq.Server {
srv := asynq.NewServer(
asynq.RedisClientOpt{Addr: redisAddr},
asynq.Config{
Concurrency: 10,
Queues: map[string]int{
"reminders": 6,
"default": 3,
},
RetryDelayFunc: asynq.DefaultRetryDelayFunc,
},
)
mux := asynq.NewServeMux()
mux.HandleFunc(TypeReminder, worker.HandleReminderTask)
go func() {
if err := srv.Run(mux); err != nil {
log.Printf("asynq worker error: %v", err)
}
}()
return srv
}