Skip to content

Commit bb7da77

Browse files
committed
fix update following children
1 parent a3aaae5 commit bb7da77

2 files changed

Lines changed: 78 additions & 52 deletions

File tree

pkg/core/core_events.go

Lines changed: 77 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -250,7 +250,6 @@ func (c *Core) updateCurrentChild(updated *Event) (*Event, error) {
250250
if !ok || parent == nil || !parent.IsParent() {
251251
return nil, fmt.Errorf("no valid parent found")
252252
}
253-
254253
if parent.Repeat == nil {
255254
return nil, fmt.Errorf("parent is not a repeating event, WTF")
256255
}
@@ -270,83 +269,110 @@ func (c *Core) updateCurrentChild(updated *Event) (*Event, error) {
270269
return c.CreateEvent(detachedEvent) // save as new
271270
}
272271

273-
// Splits the time series into two by stopping the original parent event from repeating further and creating brand new parent with updated properties.
272+
// updateFollowingChildren splits the time series into two by stopping the original parent event from repeating further and creating brand new parent with updated properties.
274273
func (c *Core) updateFollowingChildren(old, new *Event) (*Event, error) {
275-
parent, ok := c.events[new.ParentId]
274+
parent, ok := c.events[old.ParentId]
276275
if !ok || parent == nil || !parent.IsParent() {
277276
return nil, fmt.Errorf("no valid parent found")
278277
}
278+
if parent.Repeat == nil {
279+
return nil, fmt.Errorf("parent is not a repeating event")
280+
}
279281

280-
// save originals for rollback
282+
// keep originals for rollback
281283
originalRepeat := *parent.Repeat
282-
originalExceptions := append([]uuid.UUID{}, parent.Repeat.Exceptions...) // deep copy
284+
originalExceptions := append([]uuid.UUID(nil), parent.Repeat.Exceptions...)
285+
286+
// find position in the series
287+
_, splitIndex := firstOccurrenceAtOrAfter(old.From, parent)
288+
if splitIndex == -1 {
289+
return nil, fmt.Errorf("could not find occurrence at or after %s", old.From)
290+
}
291+
292+
fromDiff := new.From.Sub(old.From)
293+
294+
// editing the first occurrence -> update entire series
295+
if splitIndex == 0 {
296+
return c.updateAllChildren(old, new)
297+
}
298+
299+
// split exceptions before and after the edited occurrence
300+
exBefore, exAfter := splitExceptions(originalExceptions, old.From)
301+
302+
// remove parent before we mutate it
303+
if err := c.intervalTree.RemoveEvent(*parent); err != nil {
304+
return nil, fmt.Errorf("failed to remove parent from interval tree: %w", err)
305+
}
283306

284-
until := addUnit(old.From, -1, old.Repeat.Frequency) // cap parent at start of change
285-
if !until.After(parent.From) {
307+
// ------------ cap the original parent ------------
308+
previousStart := addUnit(old.From, -originalRepeat.Interval, originalRepeat.Frequency)
309+
if !previousStart.After(parent.From) {
310+
// only one occurrence left -> turn it into a basic (non-repeating) event
286311
parent.Repeat = nil
287312
} else {
288-
parent.Repeat.Until = until
289-
parent.Repeat.Count = 0 // enforce Until logic over Count
313+
// keep repeating but stop before the split point
314+
capped := originalRepeat
315+
capped.Until = previousStart
316+
capped.Count = 0
317+
capped.Exceptions = exBefore
318+
parent.Repeat = &capped
290319
}
291320

292-
// split exceptions
293-
exBefore, exAfter := splitExceptions(originalExceptions, new.From)
294-
if parent.Repeat != nil {
295-
parent.Repeat.Exceptions = exBefore
321+
// save the capped parent
322+
if err := c.intervalTree.InsertEvent(*parent); err != nil {
323+
parent.Repeat = &originalRepeat
324+
parent.Repeat.Exceptions = originalExceptions
325+
return nil, fmt.Errorf("failed to reinsert capped parent: %w", err)
296326
}
297327

298328
if err := c.saveAndCommitEvent(parent, fmt.Sprintf("Capped parent event %q", parent.Id)); err != nil {
299-
return nil, fmt.Errorf("failed to commit parent event: %w", err)
300-
}
301-
302-
// create the new parent for the second half of the time series
303-
newEvent := *new // shallow copy
304-
newEvent.Id = uuid.New() // assign new id
305-
newEvent.ParentId = uuid.Nil // not child anymore
306-
307-
if originalRepeat.Count != 0 && newEvent.Repeat != nil {
308-
// shorten the repeat for the second half
309-
_, elapsed := firstOccurrenceAtOrAfter(old.From, parent)
310-
if elapsed <= 0 {
311-
// the split is at the first occurance -> nothing to subtract (basically update all)
312-
newEvent.Repeat.Count = originalRepeat.Count
313-
} else {
314-
remaining := originalRepeat.Count - elapsed
315-
if remaining <= 0 {
329+
return nil, fmt.Errorf("failed to commit capped parent: %w", err)
330+
}
331+
332+
// ------------ create the new continuing series ------------
333+
newEvent := *new
334+
newEvent.Id = uuid.New()
335+
newEvent.ParentId = uuid.Nil // this is now its own parent
336+
337+
if newEvent.Repeat != nil {
338+
repeat := *newEvent.Repeat
339+
repeat.Exceptions = nil
340+
341+
// adjust count for remaining occurrences
342+
if originalRepeat.Count != 0 {
343+
remaining := originalRepeat.Count - splitIndex
344+
if remaining < 1 {
316345
remaining = 1
317346
}
318-
newEvent.Repeat.Count = remaining
347+
repeat.Count = remaining
319348
}
320-
}
321349

322-
// transform exceptions
323-
fromDiff := newEvent.From.Sub(old.From)
324-
if fromDiff != 0 {
325-
for _, exc := range exAfter {
326-
t := getTimeFromUUID(exc)
327-
t = t.Add(fromDiff)
328-
newEvent.Repeat.Exceptions = append(newEvent.Repeat.Exceptions, generateCustomUUID(newEvent.Id, t))
329-
}
330-
}
331-
if newEvent.Repeat != nil && newEvent.Repeat.Count != 0 {
332-
newEvent.Repeat.Count -= len(exAfter)
333-
if newEvent.Repeat.Count < 1 {
334-
newEvent.Repeat.Count = 1
350+
// shift exceptions to the new parent
351+
for _, ex := range exAfter {
352+
exTime := getTimeFromUUID(ex).Add(fromDiff)
353+
repeat.Exceptions = append(repeat.Exceptions, generateCustomUUID(newEvent.Id, exTime))
335354
}
355+
356+
newEvent.Repeat = &repeat
336357
}
337358

338-
createdEvent, err := c.CreateEvent(newEvent)
359+
created, err := c.CreateEvent(newEvent)
339360
if err != nil {
340-
// rollback the parent cap
361+
// rollback: restore original parent
362+
_ = c.intervalTree.RemoveEvent(*parent)
341363
parent.Repeat = &originalRepeat
342364
parent.Repeat.Exceptions = originalExceptions
343-
if rbErr := c.saveAndCommitEvent(parent, fmt.Sprintf("Rolled back cap on parent event %q", parent.Id)); rbErr != nil {
344-
return nil, fmt.Errorf("failed to create new event: %w; rollback also failed: %v", err, rbErr)
365+
366+
if rbErr := c.intervalTree.InsertEvent(*parent); rbErr != nil {
367+
return nil, fmt.Errorf("failed to create new series: %w; rollback failed too: %v", err, rbErr)
368+
}
369+
if rbErr := c.saveAndCommitEvent(parent, fmt.Sprintf("rolled back parent %q", parent.Id)); rbErr != nil {
370+
return nil, fmt.Errorf("failed to create new series: %w; rollback failed too: %v", err, rbErr)
345371
}
346-
return nil, fmt.Errorf("failed to create new event: %w", err)
372+
return nil, fmt.Errorf("failed to create new continuing series: %w", err)
347373
}
348374

349-
return createdEvent, nil
375+
return created, nil
350376
}
351377

352378
// Updates the entire repeating series by only modifying the parent. That means all generated child events get updated as well.

pkg/core/event.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Event struct {
2525
Tag string `json:"tag"` // User-defined category or label.
2626
ParentId uuid.UUID `json:"parent_id"` // Specific for child events. It is uuid.Nil if the event is basic or parent.
2727
Repeat *Repetition `json:"repeat"`
28-
UpdatedAt time.Time `json:"updated_at"` // Used for git conflict resolution; latest wins.
28+
UpdatedAt time.Time `json:"-"` // Used for git conflict resolution; latest wins. Client doesn't need to see this.
2929
}
3030

3131
// Repetition defines the recurrence rules for a Parent event.

0 commit comments

Comments
 (0)