fix: write API posts in one transaction and fire RecordSaved after its commit
POST and PUT /_journal/api/v1/posts saved the row, then the categories, tags and translations, and fired RecordSaved only when all succeeded. A failure part-way left a committed row (possibly a new slug) with no event, so subscribers such as an SEO plugin never created the redirect. The write now runs in one lagoon transaction with the event emitted inside it.
This commit is contained in:
@@ -1,6 +1,7 @@
|
||||
package api
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"html"
|
||||
"net/http"
|
||||
@@ -220,20 +221,26 @@ func Store(app *backpack.App) http.HandlerFunc {
|
||||
writeAPIError(w, http.StatusForbidden, errPublishPermission)
|
||||
return
|
||||
}
|
||||
if err := db.WithContext(r.Context()).Create(post).Error; err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, "Internal server error")
|
||||
return
|
||||
}
|
||||
if err := applyPostAssocs(r.Context(), db.WithContext(r.Context()), post, body); err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, "Internal server error")
|
||||
return
|
||||
}
|
||||
if err := loadPostAssocs(db.WithContext(r.Context()), post); err != nil {
|
||||
// D-92/D-102: the row, its associations and translations commit
|
||||
// together, and RecordSaved fires only once they have.
|
||||
err = lagoon.Transaction(r.Context(), db, func(ctx context.Context, tx *gorm.DB) error {
|
||||
if err := tx.Create(post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := applyPostAssocs(ctx, tx, post, body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := loadPostAssocs(tx, post); err != nil {
|
||||
return err
|
||||
}
|
||||
cabana.EmitRecordSaved(ctx, app, tx, postSavedEvent(ctx, tx, nil, post, body))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, "Internal server error")
|
||||
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()))})
|
||||
}
|
||||
}
|
||||
@@ -278,20 +285,26 @@ func Update(app *backpack.App) http.HandlerFunc {
|
||||
writeAPIError(w, http.StatusForbidden, errPublishPermission)
|
||||
return
|
||||
}
|
||||
if err := db.WithContext(r.Context()).Save(post).Error; err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, "Internal server error")
|
||||
return
|
||||
}
|
||||
if err := applyPostAssocs(r.Context(), db.WithContext(r.Context()), post, body); err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, "Internal server error")
|
||||
return
|
||||
}
|
||||
if err := loadPostAssocs(db.WithContext(r.Context()), post); err != nil {
|
||||
// D-92/D-102: a slug change commits with its translations and
|
||||
// associations, or not at all, and RecordSaved follows the commit.
|
||||
err = lagoon.Transaction(r.Context(), db, func(ctx context.Context, tx *gorm.DB) error {
|
||||
if err := tx.Save(post).Error; err != nil {
|
||||
return err
|
||||
}
|
||||
if err := applyPostAssocs(ctx, tx, post, body); err != nil {
|
||||
return err
|
||||
}
|
||||
if err := loadPostAssocs(tx, post); err != nil {
|
||||
return err
|
||||
}
|
||||
cabana.EmitRecordSaved(ctx, app, tx, postSavedEvent(ctx, tx, before, post, body))
|
||||
return nil
|
||||
})
|
||||
if err != nil {
|
||||
writeAPIError(w, http.StatusInternalServerError, "Internal server error")
|
||||
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()))})
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user