Revision history: change sets + revisions + revert — the undo substrate (#48) (#61)
Build image / build-and-push (push) Successful in 5s

Co-authored-by: Steve Dudenhoeffer <[email protected]>
This commit was merged in pull request #61.
This commit is contained in:
2026-07-21 05:07:30 +00:00
committed by steve
parent 653f381c4b
commit f208da94d6
16 changed files with 2169 additions and 19 deletions
+35 -4
View File
@@ -122,7 +122,23 @@ func (s *Service) CreateObject(ctx context.Context, actorID, gardenID int64, in
if err := finalizeObject(o, g); err != nil {
return nil, err
}
return s.store.CreateObject(ctx, o)
created, err := s.store.CreateObject(ctx, o)
if err != nil {
return nil, err
}
s.record(ctx, gardenID, actorID, "Added "+objectLabel(created),
changeCreate(domain.EntityObject, created.ID, created))
return created, nil
}
// objectLabel names an object for a change-set summary: its own name when it has
// one, else its kind ("bed", "grow_bag"), so history reads as "Added bed" rather
// than "Added ".
func objectLabel(o *domain.GardenObject) string {
if o.Name != "" {
return o.Name
}
return o.Kind
}
// objectForRole loads an object and enforces that the actor holds at least min
@@ -148,21 +164,36 @@ func (s *Service) UpdateObject(ctx context.Context, actorID, objectID int64, pat
return nil, err
}
before := *o // objectForRole returns the current row, and the patch mutates it
applyObjectPatch(o, patch)
if err := finalizeObject(o, g); err != nil {
return nil, err
}
o.Version = version
return s.store.UpdateObject(ctx, o)
updated, err := s.store.UpdateObject(ctx, o)
if err != nil {
return updated, err // may carry the current row on a version conflict
}
s.record(ctx, g.ID, actorID, "Edited "+objectLabel(updated),
changeUpdate(domain.EntityObject, updated.ID, &before, updated))
return updated, nil
}
// DeleteObject removes an object (its plantings cascade) from a garden the actor
// can edit.
func (s *Service) DeleteObject(ctx context.Context, actorID, objectID int64) error {
if _, _, err := s.objectForRole(ctx, actorID, objectID, roleEditor); err != nil {
o, g, err := s.objectForRole(ctx, actorID, objectID, roleEditor)
if err != nil {
return err
}
return s.store.DeleteObject(ctx, objectID)
// deleteObjectRecording snapshots the plantings the FK is about to cascade
// away, so the delete is actually revertible rather than merely listed.
changes, err := s.deleteObjectRecording(ctx, o)
if err != nil {
return err
}
s.record(ctx, g.ID, actorID, "Deleted "+objectLabel(o), changes...)
return nil
}
// GardenFull returns the whole editor payload for a garden the actor can view.