Skip to content

Commit 885051e

Browse files
committed
fix scheduling when event is updated
1 parent 669241e commit 885051e

4 files changed

Lines changed: 134 additions & 14 deletions

File tree

internal/handlers/event_handler.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -175,6 +175,22 @@ func (h *EventHandler) UpdateEvent(c *gin.Context) {
175175
return
176176
}
177177

178+
// Remove all scheduled occurrences for this event from Redis
179+
err = h.repo.RemoveEventOccurrencesFromRedis(c, event.ID)
180+
if err != nil {
181+
// Log but do not fail the request
182+
zap.L().Warn("Failed to remove old scheduled occurrences from Redis", zap.String("event_id", event.ID.String()), zap.Error(err))
183+
}
184+
185+
// Re-expand the event to schedule new occurrences
186+
go func() {
187+
if event.Schedule == nil {
188+
_ = h.expander.ExpandNonRecurringEvent(context.Background(), event)
189+
} else {
190+
_ = h.expander.ExpandRecurringEvent(context.Background(), event)
191+
}
192+
}()
193+
178194
c.JSON(http.StatusOK, event)
179195
}
180196

internal/integration/event_redis_cleanup_test.go

Lines changed: 14 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package integration
33
import (
44
"context"
55
"encoding/json"
6+
"fmt"
67
"testing"
78
"time"
89

@@ -62,16 +63,19 @@ func TestEventRepository_RedisCleanupOnDelete(t *testing.T) {
6263
for _, occ := range occurrences {
6364
data, err := json.Marshal(occ)
6465
require.NoError(t, err)
66+
key := "schedule:" + event.ID.String() + ":" + fmt.Sprintf("%d", occ.ScheduledAt.Unix())
6567
score := float64(occ.ScheduledAt.UnixMilli())
66-
_, err = redisClient.ZAdd(ctx, "schedule:events", redis.Z{
68+
_, err = redisClient.ZAdd(ctx, "schedules", redis.Z{
6769
Score: score,
68-
Member: string(data),
70+
Member: key,
6971
}).Result()
7072
require.NoError(t, err)
73+
_, err = redisClient.HSet(ctx, "schedule:data", key, data).Result()
74+
require.NoError(t, err)
7175
}
7276

7377
// Ensure occurrences are in Redis
74-
results, err := redisClient.ZRange(ctx, "schedule:events", 0, -1).Result()
78+
results, err := redisClient.ZRange(ctx, "schedules", 0, -1).Result()
7579
require.NoError(t, err)
7680
assert.NotEmpty(t, results)
7781

@@ -80,11 +84,16 @@ func TestEventRepository_RedisCleanupOnDelete(t *testing.T) {
8084
require.NoError(t, err)
8185

8286
// Ensure occurrences for this event are removed from Redis
83-
results, err = redisClient.ZRange(ctx, "schedule:events", 0, -1).Result()
87+
results, err = redisClient.ZRange(ctx, "schedules", 0, -1).Result()
8488
require.NoError(t, err)
8589
for _, res := range results {
90+
// Fetch from hash instead of decoding directly
91+
data, err := redisClient.HGet(ctx, "schedule:data", res).Result()
92+
if err != nil {
93+
continue // skip if not found
94+
}
8695
var occ models.Occurrence
87-
err := json.Unmarshal([]byte(res), &occ)
96+
err = json.Unmarshal([]byte(data), &occ)
8897
if err == nil {
8998
assert.NotEqual(t, event.ID, occ.EventID, "Occurrence for deleted event should be removed from Redis")
9099
}

internal/repository/event_repository.go

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -164,27 +164,36 @@ func (r *EventRepository) Delete(ctx context.Context, id uuid.UUID) error {
164164
}
165165

166166
// Remove all scheduled occurrences for this event from Redis
167-
err = r.removeEventOccurrencesFromRedis(ctx, id)
167+
err = r.RemoveEventOccurrencesFromRedis(ctx, id)
168168
if err != nil {
169169
r.logger.Warn("Failed to remove event occurrences from Redis", zap.String("event_id", id.String()), zap.Error(err))
170170
}
171171

172172
return nil
173173
}
174174

175-
// removeEventOccurrencesFromRedis removes all scheduled occurrences for an event from Redis
176-
func (r *EventRepository) removeEventOccurrencesFromRedis(ctx context.Context, eventID uuid.UUID) error {
177-
results, err := r.redis.ZRange(ctx, "schedule:events", 0, -1).Result()
175+
// RemoveEventOccurrencesFromRedis removes all scheduled occurrences for an event from Redis
176+
func (r *EventRepository) RemoveEventOccurrencesFromRedis(ctx context.Context, eventID uuid.UUID) error {
177+
results, err := r.redis.ZRange(ctx, "schedules", 0, -1).Result()
178178
if err != nil {
179179
return err
180180
}
181-
for _, res := range results {
182-
var occ models.Occurrence
183-
if err := json.Unmarshal([]byte(res), &occ); err != nil {
181+
for _, key := range results {
182+
// Fetch the schedule data from the hash
183+
data, err := r.redis.HGet(ctx, "schedule:data", key).Result()
184+
if err != nil {
185+
continue // skip if not found or error
186+
}
187+
var sched struct {
188+
EventID uuid.UUID `json:"event_id"`
189+
}
190+
if err := json.Unmarshal([]byte(data), &sched); err != nil {
184191
continue // skip invalid
185192
}
186-
if occ.EventID == eventID {
187-
r.redis.ZRem(ctx, "schedule:events", res)
193+
if sched.EventID == eventID {
194+
// Remove from both sorted set and hash
195+
r.redis.ZRem(ctx, "schedules", key)
196+
r.redis.HDel(ctx, "schedule:data", key)
188197
}
189198
}
190199
return nil

internal/scheduler/expander_test.go

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -241,4 +241,90 @@ func TestEventExpander(t *testing.T) {
241241
}
242242
assert.Equal(t, expectedMondays, scheduledMondays)
243243
})
244+
245+
t.Run("update_event_cleans_and_reexpands", func(t *testing.T) {
246+
cleanup()
247+
fixedNow := time.Date(2024, 6, 1, 10, 0, 0, 0, time.UTC) // Saturday
248+
lookAheadDuration := 14 * 24 * time.Hour // 2 weeks
249+
gracePeriod := 2 * time.Minute
250+
expansionInterval := 5 * time.Minute
251+
252+
oldNow := TimeNow
253+
TimeNow = func() time.Time { return fixedNow }
254+
defer func() { TimeNow = oldNow }()
255+
256+
eventStart := fixedNow.AddDate(0, 0, -7) // 1 week ago
257+
scheduleConfig := &models.ScheduleConfig{
258+
Frequency: "weekly",
259+
Interval: 1,
260+
ByDay: []string{"MO"}, // Every Monday
261+
}
262+
event := &models.Event{
263+
ID: uuid.New(),
264+
Name: "Weekly Event",
265+
Description: "Weekly event for update test",
266+
StartTime: eventStart,
267+
Webhook: "http://example.com",
268+
Schedule: scheduleConfig,
269+
Status: models.EventStatusActive,
270+
Metadata: []byte(`{"key": "value"}`),
271+
Tags: pq.StringArray{"test"},
272+
CreatedAt: eventStart,
273+
}
274+
err := eventRepo.Create(ctx, event)
275+
require.NoError(t, err)
276+
277+
expander := NewExpander(scheduler, eventRepo, occurrenceRepo, lookAheadDuration, expansionInterval, gracePeriod, logger)
278+
err = expander.ExpandEvents(ctx)
279+
require.NoError(t, err)
280+
281+
// Check that Monday is scheduled
282+
results, err := redisClient.ZRange(ctx, ScheduleKey, 0, -1).Result()
283+
require.NoError(t, err)
284+
var scheduledDays []time.Weekday
285+
for _, key := range results {
286+
data, err := redisClient.HGet(ctx, "schedule:data", key).Result()
287+
require.NoError(t, err)
288+
var sched models.Schedule
289+
err = json.Unmarshal([]byte(data), &sched)
290+
require.NoError(t, err)
291+
if sched.EventID == event.ID {
292+
scheduledDays = append(scheduledDays, sched.ScheduledAt.Weekday())
293+
}
294+
}
295+
assert.Contains(t, scheduledDays, time.Monday)
296+
assert.NotContains(t, scheduledDays, time.Wednesday)
297+
298+
// Update event to schedule on Wednesday instead
299+
event.Schedule = &models.ScheduleConfig{
300+
Frequency: "weekly",
301+
Interval: 1,
302+
ByDay: []string{"WE"}, // Every Wednesday
303+
}
304+
err = eventRepo.Update(ctx, event)
305+
require.NoError(t, err)
306+
// Remove old occurrences
307+
err = eventRepo.RemoveEventOccurrencesFromRedis(ctx, event.ID)
308+
require.NoError(t, err)
309+
// Re-expand
310+
err = expander.ExpandEvents(ctx)
311+
require.NoError(t, err)
312+
313+
// Check that only Wednesday is scheduled
314+
results, err = redisClient.ZRange(ctx, ScheduleKey, 0, -1).Result()
315+
require.NoError(t, err)
316+
scheduledDays = scheduledDays[:0]
317+
for _, key := range results {
318+
data, err := redisClient.HGet(ctx, "schedule:data", key).Result()
319+
require.NoError(t, err)
320+
var sched models.Schedule
321+
err = json.Unmarshal([]byte(data), &sched)
322+
require.NoError(t, err)
323+
if sched.EventID == event.ID {
324+
scheduledDays = append(scheduledDays, sched.ScheduledAt.Weekday())
325+
}
326+
}
327+
assert.Contains(t, scheduledDays, time.Wednesday)
328+
assert.NotContains(t, scheduledDays, time.Monday)
329+
})
244330
}

0 commit comments

Comments
 (0)