feat(19.3-04): purge the host site's page cache after menu and menu-entry saves, deletes and reorders
- menus controller gains the app accessor and FormAfterCreate/Update/Delete purging kind menu with the menu code - menu entries purge kind menu_entry with the entry id after the placement checks pass, on delete, and through pact.ReorderAfterSave with partition:<menu id> - purgeSiteCache helper on cabana.TxFromContext and cooler.SiteCacheFromApp - TestMenuAdminPurgesSiteCache on Postgres
This commit is contained in:
@@ -15,7 +15,7 @@ const PermissionManageMenu = "golem15.menu.manage_menu"
|
||||
// a database handle or registry of its own.
|
||||
func AdminControllers(app func() *backpack.App) []pact.AdminController {
|
||||
return []pact.AdminController{
|
||||
menusAdminController{},
|
||||
menusAdminController{app: app},
|
||||
menuEntriesAdminController{app: app},
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package controllers
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"git.golem15.com/golem15/sm-menu-plugin/classes"
|
||||
@@ -186,16 +187,22 @@ func (c menuEntriesAdminController) checkEntry(model any) error {
|
||||
// FormAfterCreate refuses an entry that ends up in no menu (no menu chosen
|
||||
// and no parent to take it from). cabana writes the menu and parent from the
|
||||
// relation fields after the model rules ran, so the check runs once the row
|
||||
// is written, inside the save's transaction.
|
||||
func (menuEntriesAdminController) FormAfterCreate(ctx context.Context, model any) error {
|
||||
return checkPlacement(ctx, model)
|
||||
// is written, inside the save's transaction. An entry that passes purges the
|
||||
// host site's page caches once the save commits.
|
||||
func (c menuEntriesAdminController) FormAfterCreate(ctx context.Context, model any) error {
|
||||
if err := checkPlacement(ctx, model); err != nil {
|
||||
return err
|
||||
}
|
||||
c.purge(ctx, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormAfterUpdate also refuses a parent that is the entry itself or one of
|
||||
// its descendants, then moves the entry's descendants into its menu, so a
|
||||
// subtree follows its root to another menu instead of staying behind with
|
||||
// parents in a menu it is not in.
|
||||
func (menuEntriesAdminController) FormAfterUpdate(ctx context.Context, model any) error {
|
||||
// parents in a menu it is not in. An update that passes purges the host
|
||||
// site's page caches once the save commits.
|
||||
func (c menuEntriesAdminController) FormAfterUpdate(ctx context.Context, model any) error {
|
||||
if err := checkPlacement(ctx, model); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -207,13 +214,43 @@ func (menuEntriesAdminController) FormAfterUpdate(ctx context.Context, model any
|
||||
if !ok {
|
||||
return errors.New("golem15.menu: the entry check needs the save's transaction")
|
||||
}
|
||||
return tx.Session(&gorm.Session{NewDB: true}).WithContext(ctx).Exec(`WITH RECURSIVE sub(id) AS (
|
||||
err := tx.Session(&gorm.Session{NewDB: true}).WithContext(ctx).Exec(`WITH RECURSIVE sub(id) AS (
|
||||
SELECT id FROM golem15_menu_menu_entries WHERE parent_id = ? AND id <> ?
|
||||
UNION
|
||||
SELECT e.id FROM golem15_menu_menu_entries e JOIN sub ON e.parent_id = sub.id
|
||||
)
|
||||
UPDATE golem15_menu_menu_entries SET menu_id = ? WHERE id IN (SELECT id FROM sub) AND id <> ? AND menu_id <> ?`,
|
||||
entry.ID, entry.ID, entry.MenuID, entry.ID, entry.MenuID).Error
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.purge(ctx, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormAfterDelete purges the host site's page caches once the delete
|
||||
// commits.
|
||||
func (c menuEntriesAdminController) FormAfterDelete(ctx context.Context, model any) error {
|
||||
c.purge(ctx, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
// ReorderAfterSave purges the host site's page caches once the reorder of
|
||||
// one menu's entries commits; the slug names the partition (the menu id).
|
||||
func (c menuEntriesAdminController) ReorderAfterSave(ctx context.Context, partition *uint64) error {
|
||||
slug := "reorder"
|
||||
if partition != nil {
|
||||
slug = "partition:" + strconv.FormatUint(*partition, 10)
|
||||
}
|
||||
purgeSiteCache(ctx, c.app, siteCacheKindEntry, slug)
|
||||
return nil
|
||||
}
|
||||
|
||||
// purge registers a purge of kind menu_entry with the entry's id.
|
||||
func (c menuEntriesAdminController) purge(ctx context.Context, model any) {
|
||||
if entry, ok := model.(*models.MenuEntry); ok && entry != nil {
|
||||
purgeSiteCache(ctx, c.app, siteCacheKindEntry, strconv.FormatUint(uint64(entry.ID), 10))
|
||||
}
|
||||
}
|
||||
|
||||
// checkPlacement refuses an entry in no menu, a parent from another menu
|
||||
@@ -267,6 +304,8 @@ var (
|
||||
_ pact.FormBeforeUpdate = menuEntriesAdminController{}
|
||||
_ pact.FormAfterCreate = menuEntriesAdminController{}
|
||||
_ pact.FormAfterUpdate = menuEntriesAdminController{}
|
||||
_ pact.FormAfterDelete = menuEntriesAdminController{}
|
||||
_ pact.ReorderAfterSave = menuEntriesAdminController{}
|
||||
_ pact.RelationExtendOptionsQuery = menuEntriesAdminController{}
|
||||
_ cabana.FieldRelationProvider = menuEntriesAdminController{}
|
||||
)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.golem15.com/golem15/sm-menu-plugin/models"
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/pact"
|
||||
)
|
||||
|
||||
@@ -10,7 +13,9 @@ const MenusControllerID = "golem15.menu.menus"
|
||||
|
||||
// menusAdminController lists and edits menus: name, code, CSS class and the
|
||||
// active switch.
|
||||
type menusAdminController struct{}
|
||||
type menusAdminController struct {
|
||||
app func() *backpack.App
|
||||
}
|
||||
|
||||
func (menusAdminController) ID() string { return MenusControllerID }
|
||||
|
||||
@@ -24,8 +29,43 @@ func (menusAdminController) RequiredPermissions() []string {
|
||||
|
||||
func (menusAdminController) NewRecord() any { return &models.Menu{} }
|
||||
|
||||
// Site-cache purge kinds of the menu plugin's saves.
|
||||
const (
|
||||
siteCacheKindMenu = "menu"
|
||||
siteCacheKindEntry = "menu_entry"
|
||||
)
|
||||
|
||||
// FormAfterCreate purges the host site's page caches once the save commits.
|
||||
func (c menusAdminController) FormAfterCreate(ctx context.Context, model any) error {
|
||||
c.purge(ctx, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormAfterUpdate purges the host site's page caches once the save commits.
|
||||
func (c menusAdminController) FormAfterUpdate(ctx context.Context, model any) error {
|
||||
c.purge(ctx, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
// FormAfterDelete purges the host site's page caches once the delete
|
||||
// commits.
|
||||
func (c menusAdminController) FormAfterDelete(ctx context.Context, model any) error {
|
||||
c.purge(ctx, model)
|
||||
return nil
|
||||
}
|
||||
|
||||
// purge registers a purge of kind menu with the menu's code.
|
||||
func (c menusAdminController) purge(ctx context.Context, model any) {
|
||||
if menu, ok := model.(*models.Menu); ok && menu != nil {
|
||||
purgeSiteCache(ctx, c.app, siteCacheKindMenu, menu.Code)
|
||||
}
|
||||
}
|
||||
|
||||
var (
|
||||
_ pact.AdminController = menusAdminController{}
|
||||
_ pact.AdminPermissioned = menusAdminController{}
|
||||
_ pact.AdminRecordSource = menusAdminController{}
|
||||
_ pact.FormAfterCreate = menusAdminController{}
|
||||
_ pact.FormAfterUpdate = menusAdminController{}
|
||||
_ pact.FormAfterDelete = menusAdminController{}
|
||||
)
|
||||
|
||||
32
controllers/site_cache.go
Normal file
32
controllers/site_cache.go
Normal file
@@ -0,0 +1,32 @@
|
||||
package controllers
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/backpack"
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/cooler"
|
||||
)
|
||||
|
||||
// purgeSiteCache registers one site-cache purge of kind and slug, sent by
|
||||
// the framework's purger (site_cache.purge_url and purge_token) after the
|
||||
// save transaction of ctx commits. Without a purge configured, or outside a
|
||||
// save transaction, it does nothing. A purge never fails the save.
|
||||
func purgeSiteCache(ctx context.Context, app func() *backpack.App, kind, slug string) {
|
||||
if app == nil {
|
||||
return
|
||||
}
|
||||
a := app()
|
||||
if a == nil {
|
||||
return
|
||||
}
|
||||
tx, ok := cabana.TxFromContext(ctx)
|
||||
if !ok {
|
||||
return
|
||||
}
|
||||
purger := cooler.SiteCacheFromApp(a)
|
||||
if purger == nil {
|
||||
return
|
||||
}
|
||||
cooler.PurgeSiteCacheAfterCommit(ctx, tx, purger, kind, slug)
|
||||
}
|
||||
Reference in New Issue
Block a user