Files

161 lines
5.0 KiB
Go

package controllers
import (
"context"
"strconv"
"strings"
"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/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))
}
// FormAfterDelete records the deleted rule in history inside the delete's
// transaction, so its values can be restored.
func (c rulesAdminController) FormAfterDelete(ctx context.Context, model any) error {
r, ok := model.(*models.Rule)
if !ok || r == nil {
return nil
}
svc, err := classes.NewService(appOf(c.app))
if err != nil {
return err
}
db, err := database(ctx, c.app)
if err != nil {
return err
}
return svc.Record(ctx, db, classes.Change{
Kind: models.SubjectRule,
SubjectID: strconv.FormatUint(uint64(r.ID), 10),
What: svc.Phrase("golem15.seo::lang.history.what_rule", map[string]string{"url": r.URL}),
Before: classes.RuleValues(*r),
})
}
// 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
}
// service returns the SEO service of the booted app, nil before boot.
func (c rulesAdminController) service() *classes.Service {
svc, err := classes.NewService(appOf(c.app))
if err != nil {
return nil
}
return svc
}
// AdminJS loads the SEO element bundle on the rules screens (the result
// preview widget).
func (rulesAdminController) AdminJS() []string { return []string{"assets/dist/seo.js"} }
// AdminCSS loads the SEO element styles on the rules screens.
func (rulesAdminController) AdminCSS() []string { return []string{"assets/dist/seo.css"} }
// AdminActions registers the result preview's widget action (E3).
func (c rulesAdminController) AdminActions() []pact.AdminAction {
return []pact.AdminAction{classes.RuleSnippetAction(c.service, "golem15.seo::lang.snippet.action", PermissionManage)}
}
// PartialData serves P1 seo_status (canonical warnings) and P2
// seo_effective of a rule.
func (c rulesAdminController) PartialData(ctx context.Context, name string, record any) (any, error) {
svc := c.service()
r, ok := record.(*models.Rule)
if svc == nil || !ok || r == nil {
if name == "seo_effective" {
return classes.EffectiveView{}, nil
}
return classes.StatusView{}, nil
}
switch name {
case "seo_status":
return svc.RuleStatus(ctx, *r), nil
case "seo_effective":
return svc.RuleEffective(ctx, *r)
}
return nil, 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{}
_ pact.FormAfterDelete = rulesAdminController{}
_ pact.HasAdminActions = rulesAdminController{}
_ pact.AdminPartialData = rulesAdminController{}
_ pact.AdminClientAssets = rulesAdminController{}
)