Files
sm-journal-plugin/controllers/categories.go
Jakub Zych f3b223f131 feat(16.2-07): purge the host site cache after a category admin update
- classes.SiteCachePurger posts {kind, slug} with a bearer token to
  golem15.journal.site_cache.purge_url, 2 s timeout, logged, never fails a save
- PurgeSiteCacheAfterCommit runs it through lagoon.AfterCommit
- Boot builds and publishes the purger from the two config keys
- categories FormAfterUpdate registers the purge; Postgres test proves
  one authorized request after the commit
2026-10-10 18:19:58 +02:00

101 lines
3.4 KiB
Go

package controllers
import (
"context"
"errors"
"strings"
"git.golem15.com/golem15/sm-journal-plugin/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/cabana"
"git.golem15.com/golem15/summercms/modules/pact"
)
const (
// CategoriesControllerID is the cabana controller id of the Categories screen.
CategoriesControllerID = "golem15.journal.categories"
// PermissionAccessCategories is Categories::$requiredPermissions.
PermissionAccessCategories = "golem15.journal.access_categories"
)
type categoriesAdminController struct {
app func() *backpack.App
}
func (categoriesAdminController) ID() string { return CategoriesControllerID }
func (categoriesAdminController) ModelName() string { return `Golem15\Journal\Models\Category` }
func (categoriesAdminController) ConfigDir() string { return "controllers/categories" }
func (categoriesAdminController) RequiredPermissions() []string {
return []string{PermissionAccessCategories}
}
func (categoriesAdminController) NewRecord() any { return &models.Category{} }
func (categoriesAdminController) AdminFieldRelations() []cabana.FieldRelationContract {
return []cabana.FieldRelationContract{{
Field: "parent",
Kind: "belongsTo",
NewRelated: func() any { return &models.Category{} },
ForeignKey: "parent_id",
LabelColumn: "name",
}}
}
func (categoriesAdminController) FormBeforeCreate(ctx context.Context, model any) error {
cat, ok := model.(*models.Category)
if !ok || cat == nil {
return errors.New("categories admin: form model is not a category")
}
return prepareCategoryWrite(cat)
}
func (categoriesAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
cat, ok := model.(*models.Category)
if !ok || cat == nil {
return errors.New("categories admin: form model is not a category")
}
return prepareCategoryWrite(cat)
}
// FormAfterUpdate purges the host site's page caches once the save
// transaction commits (G-16.2-2, D-19).
func (c categoriesAdminController) FormAfterUpdate(ctx context.Context, model any) error {
cat, ok := model.(*models.Category)
if !ok || cat == nil {
return errors.New("categories admin: form model is not a category")
}
purgeSiteCache(ctx, c.app, "category", cat.Slug)
return nil
}
// prepareCategoryWrite stores blank category content (white space only) as
// NULL and accepts any other content it receives. Since Phase 16.2 the
// content field is the blogeditor type, whose Decode already stored the HTML
// policy's output (classes.SanitizeHTMLReport) and told the administrator
// what it removed, so an admin save is never refused because of markup
// (D-10 amended, see the bluemonday decision note). Rows written by SQL or
// by the import commands do not pass through here and are not sanitized by
// it.
func prepareCategoryWrite(cat *models.Category) error {
if cat.Content == nil {
return nil
}
if strings.TrimSpace(*cat.Content) == "" {
cat.Content = nil
}
return nil
}
var (
_ pact.AdminController = categoriesAdminController{}
_ pact.AdminPermissioned = categoriesAdminController{}
_ pact.AdminRecordSource = categoriesAdminController{}
_ pact.FormBeforeCreate = categoriesAdminController{}
_ pact.FormBeforeUpdate = categoriesAdminController{}
_ pact.FormAfterUpdate = categoriesAdminController{}
_ cabana.FieldRelationProvider = categoriesAdminController{}
)