Files
sm-seo-plugin/controllers/settings.go

132 lines
4.4 KiB
Go

package controllers
import (
"context"
"errors"
"git.golem15.com/golem15/sm-seo-plugin/classes"
"git.golem15.com/golem15/sm-seo-plugin/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/diary"
"git.golem15.com/golem15/summercms/modules/lagoon"
"git.golem15.com/golem15/summercms/modules/pact"
"gorm.io/gorm"
"gorm.io/gorm/clause"
)
// log is the controllers' logger (component=seo).
var log = diary.For("seo")
// settingsAdminController edits the SEO settings singleton (D-104), linked
// from the SEO settings tile. Its per-content-type tabs are added by the
// plugin's own form extension on this controller.
type settingsAdminController struct {
app func() *backpack.App
}
func (settingsAdminController) ID() string { return SettingsControllerID }
func (settingsAdminController) ModelName() string { return `Golem15\SEO\Models\Settings` }
func (settingsAdminController) ConfigDir() string { return "controllers/settings" }
func (settingsAdminController) NewRecord() any { return &models.Settings{} }
func (settingsAdminController) RequiredPermissions() []string {
return []string{PermissionManage}
}
// SingletonRecord returns settings row 1, creating it with the defaults
// when the migration's row is gone (first or create, safe against a
// concurrent first request through ON CONFLICT DO NOTHING).
func (settingsAdminController) SingletonRecord(_ context.Context, db *gorm.DB) (any, error) {
if db == nil {
return nil, errors.New("golem15.seo: database is not configured")
}
var rows []models.Settings
if err := db.Where("id = ?", 1).Limit(1).Find(&rows).Error; err != nil {
return nil, err
}
if len(rows) > 0 {
return &rows[0], nil
}
row := models.DefaultSettings()
if err := db.Clauses(clause.OnConflict{DoNothing: true}).Create(&row).Error; err != nil {
return nil, err
}
if err := db.Where("id = ?", 1).Limit(1).Find(&rows).Error; err != nil || len(rows) == 0 {
return nil, errors.New("golem15.seo: settings row is missing")
}
return &rows[0], nil
}
// FormBeforeUpdate refuses templates with unknown placeholders and
// malformed or duplicate verification file names.
func (c settingsAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
s, ok := model.(*models.Settings)
if !ok || s == nil {
return nil
}
return problemsError(ctx, translator(c.app), s.Problems())
}
// FormBeforeCreate checks a created row like an update.
func (c settingsAdminController) FormBeforeCreate(ctx context.Context, model any) error {
return c.FormBeforeUpdate(ctx, model)
}
// FormAfterUpdate queues the entry generation job once the save commits,
// so the effective title and description cache follows new templates. A
// queue error is logged and never fails the save.
func (c settingsAdminController) FormAfterUpdate(ctx context.Context, _ any) error {
app := appOf(c.app)
svc, err := classes.NewService(app)
if err != nil {
return nil
}
tx, err := database(ctx, c.app)
if err != nil {
return nil
}
lagoon.AfterCommit(ctx, tx, func(ctx context.Context, _ *gorm.DB) {
db, ok := app.Lookup[*gorm.DB]()
if !ok || db == nil {
return
}
if _, err := svc.EnqueueGenerate(ctx, db.WithContext(ctx), ""); err != nil {
log.Warn("golem15.seo: queue entry generation", "error", err)
}
})
return nil
}
// stagingView is the view model of the staging partial.
type stagingView struct {
Staging bool
}
// PartialData serves the staging callout: shown outside production, where
// every page is noindex whatever the settings say (D-22).
func (c settingsAdminController) PartialData(_ context.Context, name string, _ any) (any, error) {
if name != "staging" {
return nil, nil
}
return stagingView{Staging: IsStaging(appOf(c.app))}, nil
}
// IsStaging reports whether the application runs outside production. It
// depends on the environment only (D-22): no setting can change it.
func IsStaging(app *backpack.App) bool {
if app == nil || app.Config == nil {
return true
}
return app.Config.Environment() != "production"
}
var (
_ pact.AdminController = settingsAdminController{}
_ pact.AdminPermissioned = settingsAdminController{}
_ pact.AdminRecordSource = settingsAdminController{}
_ pact.AdminSingleton = settingsAdminController{}
_ pact.AdminPartialData = settingsAdminController{}
_ pact.FormBeforeCreate = settingsAdminController{}
_ pact.FormBeforeUpdate = settingsAdminController{}
_ pact.FormAfterUpdate = settingsAdminController{}
)