feat: URL rules and the settings singleton

This commit is contained in:
2026-10-27 10:34:39 +01:00
parent fb9590f099
commit ce60c8a1ee
20 changed files with 1412 additions and 0 deletions

90
controllers/rules.go Normal file
View File

@@ -0,0 +1,90 @@
package controllers
import (
"context"
"strings"
"git.golem15.com/golem15/sm-seo-plugin/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/pact"
"gorm.io/gorm"
)
// rulesAdminController lists and edits URL rules (D-02, UI-SPEC C7).
type rulesAdminController struct {
app func() *backpack.App
}
func (rulesAdminController) ID() string { return RulesControllerID }
func (rulesAdminController) ModelName() string { return `Golem15\SEO\Models\Rule` }
func (rulesAdminController) ConfigDir() string { return "controllers/rules" }
func (rulesAdminController) NewRecord() any { return &models.Rule{} }
func (rulesAdminController) RequiredPermissions() []string {
return []string{PermissionManage}
}
// FormBeforeCreate trims the address, normalises the match and refuses an
// invalid or taken address.
func (c rulesAdminController) FormBeforeCreate(ctx context.Context, model any) error {
return c.check(ctx, model)
}
// FormBeforeUpdate checks an update like a create.
func (c rulesAdminController) FormBeforeUpdate(ctx context.Context, model any) error {
return c.check(ctx, model)
}
func (c rulesAdminController) check(ctx context.Context, model any) error {
r, ok := model.(*models.Rule)
if !ok || r == nil {
return nil
}
r.URL = strings.TrimSpace(r.URL)
r.Match = models.NormalizeMatch(r.Match)
db, err := database(ctx, c.app)
if err != nil {
return err
}
return problemsError(ctx, translator(c.app), r.Problems(db))
}
// ListExtendRecords fills the overrides column: the translated labels of
// the properties each rule sets, comma-joined.
func (c rulesAdminController) ListExtendRecords(ctx context.Context, _ *gorm.DB, records []any) error {
tr := translator(c.app)
for _, record := range records {
r, ok := record.(*models.Rule)
if !ok || r == nil {
continue
}
var labels []string
for _, p := range models.RuleProperties {
if strings.TrimSpace(r.Value(p.Column)) != "" {
labels = append(labels, Phrase(ctx, tr, p.Label, nil))
}
}
r.Overrides = strings.Join(labels, ", ")
}
return nil
}
// FilterOptions serves the match filter's two choices.
func (rulesAdminController) FilterOptions(scope string) []pact.Option {
if scope != models.FilterMatch {
return nil
}
return []pact.Option{
{Value: models.MatchExact, Label: "golem15.seo::lang.rules.match_exact"},
{Value: models.MatchPattern, Label: "golem15.seo::lang.rules.match_pattern"},
}
}
var (
_ pact.AdminController = rulesAdminController{}
_ pact.AdminPermissioned = rulesAdminController{}
_ pact.AdminRecordSource = rulesAdminController{}
_ pact.FormBeforeCreate = rulesAdminController{}
_ pact.FormBeforeUpdate = rulesAdminController{}
_ pact.ListExtendRecords = rulesAdminController{}
_ pact.FilterOptions = rulesAdminController{}
)