fix: IndexNow hears of deleted and edited records

RecordDeleted marks the listed rows it excludes and RecordSaved the rows
whose lastmod it moves (indexnow_pending_at); the next rebuild sends
them to IndexNow as removed or changed and clears the marks. RecordSaved
checks for a nil event before reading it. TestIndexNow now deletes and
edits through the record events, as the admin does.
This commit is contained in:
Jakub Zych
2026-11-04 23:02:09 +01:00
parent 6b67ab759b
commit f58d55555c
8 changed files with 147 additions and 12 deletions

View File

@@ -106,12 +106,16 @@ func RealChanges(changes []pact.RecordChange, ignore map[string]bool) Changed {
// RecordSaved handles a saved record of a content type (D-30): when the
// save changed an attribute outside golem15.sitemap.lastmod_ignore, the
// lastmod of the record's rows moves to now (every locale's for a change
// on the record's own row, else only the changed locales'), and a
// debounced rebuild is queued. A model no content type declares is
// ignored.
// on the record's own row, else only the changed locales'), the same rows
// are marked for IndexNow (indexnow_pending_at, D-31: the next rebuild
// sends the listed ones as changed), and a debounced rebuild is queued. A
// model no content type declares, or a nil event, is ignored.
func RecordSaved(ctx context.Context, app *backpack.App, ev *pact.RecordSaved) error {
if ev == nil {
return nil
}
typ, db, ok := contentTypeOf(app, ev.Model)
if !ok || ev == nil {
if !ok {
return nil
}
changed := RealChanges(ev.Changes, LastmodIgnore(app.Config))
@@ -128,14 +132,16 @@ func RecordSaved(ctx context.Context, app *backpack.App, ev *pact.RecordSaved) e
}
q = q.Where("locale IN ?", locales)
}
if err := q.Updates(map[string]any{"lastmod": now, "updated_at": now}).Error; err != nil {
if err := q.Updates(map[string]any{"lastmod": now, "updated_at": now, "indexnow_pending_at": now}).Error; err != nil {
return err
}
return RequestDebouncedRebuild(ctx, app, db)
}
// RecordDeleted handles a deleted record of a content type: its rows leave
// the files at once (reason deleted, their file numbers kept as holes) and
// the files at once (reason deleted, their file numbers kept as holes),
// those that were listed are marked for IndexNow in the same update
// (indexnow_pending_at, D-31: the next rebuild sends them as removed), and
// a debounced rebuild is queued.
func RecordDeleted(ctx context.Context, app *backpack.App, ev *pact.RecordDeleted) error {
if ev == nil {
@@ -149,7 +155,11 @@ func RecordDeleted(ctx context.Context, app *backpack.App, ev *pact.RecordDelete
now := time.Now().UTC()
if err := db.Model(&models.URL{}).
Where("type_key = ? AND record_id = ? AND excluded_reason <> ?", typ, ev.ID, models.ReasonDeleted).
Updates(map[string]any{"excluded_reason": models.ReasonDeleted, "updated_at": now}).Error; err != nil {
Updates(map[string]any{
"excluded_reason": models.ReasonDeleted,
"updated_at": now,
"indexnow_pending_at": gorm.Expr("CASE WHEN excluded_reason = '' THEN ?::timestamptz ELSE indexnow_pending_at END", now),
}).Error; err != nil {
return err
}
return RequestDebouncedRebuild(ctx, app, db)