feat: record events from the Journal API writes (D-102)

- POST/PUT/DELETE /_journal/api/v1/posts fire RecordSaved/RecordDeleted
  through cabana.EmitRecordSaved/EmitRecordDeleted, so a slug changed over
  MCP reaches subscribers like an admin save
- PUT reports the changed columns and each submitted translation that
  changed, read before the write (a translated slug per locale)
- TestAPIRecordEvents covers POST, PUT slug change and DELETE
This commit is contained in:
2026-10-26 22:35:08 +01:00
parent 08ab15338c
commit 4b3a8bb049
4 changed files with 258 additions and 0 deletions

View File

@@ -16,6 +16,7 @@ import (
"git.golem15.com/golem15/summercms/modules/cooler"
"git.golem15.com/golem15/summercms/modules/lagoon"
"git.golem15.com/golem15/summercms/modules/lagoon/attach"
"git.golem15.com/golem15/summercms/modules/pact"
"git.golem15.com/golem15/summercms/modules/wire"
"gocloud.dev/blob"
"gorm.io/gorm"
@@ -232,6 +233,7 @@ func Store(app *backpack.App) http.HandlerFunc {
return
}
purgeSiteCache(r, app, post)
cabana.EmitRecordSaved(r.Context(), app, nil, postSavedEvent(r.Context(), db.WithContext(r.Context()), nil, post, body))
wire.WriteJSON(w, http.StatusCreated, map[string]any{"data": serializePost(post, db.WithContext(r.Context()))})
}
}
@@ -265,6 +267,9 @@ func Update(app *backpack.App) http.HandlerFunc {
writeAPIError(w, http.StatusBadRequest, errValidationFailed)
return
}
// D-92/D-102: the values before the write, for the RecordSaved
// subscribers hear (a slug changed over MCP gets its redirect).
before := snapshotPost(r.Context(), db.WithContext(r.Context()), post, body)
if verrs := applyPostUpdate(r, db.WithContext(r.Context()), post, body); verrs != nil {
writeValidation(w, verrs)
return
@@ -286,6 +291,7 @@ func Update(app *backpack.App) http.HandlerFunc {
return
}
purgeSiteCache(r, app, post)
cabana.EmitRecordSaved(r.Context(), app, nil, postSavedEvent(r.Context(), db.WithContext(r.Context()), before, post, body))
wire.WriteJSON(w, http.StatusOK, map[string]any{"data": serializePost(post, db.WithContext(r.Context()))})
}
}
@@ -319,6 +325,10 @@ func Destroy(app *backpack.App) http.HandlerFunc {
return
}
purgeSiteCache(r, app, post)
cabana.EmitRecordDeleted(r.Context(), app, nil, pact.RecordDeleted{
Model: post.MorphName(),
ID: strconv.FormatUint(uint64(post.ID), 10),
})
wire.WriteJSON(w, http.StatusOK, map[string]any{"message": msgPostDeleted})
}
}