140 lines
4.6 KiB
Go
140 lines
4.6 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), and the P3
|
|
// placeholder table.
|
|
func (c settingsAdminController) PartialData(ctx context.Context, name string, _ any) (any, error) {
|
|
switch name {
|
|
case "staging":
|
|
return stagingView{Staging: IsStaging(appOf(c.app))}, nil
|
|
case "placeholders":
|
|
svc, err := classes.NewService(appOf(c.app))
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return svc.Placeholders(ctx), nil
|
|
}
|
|
return nil, 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{}
|
|
)
|