// Package controllers holds the SEO plugin's admin controllers. package controllers import ( "context" "errors" "git.golem15.com/golem15/sm-seo-plugin/models" "git.golem15.com/golem15/summercms/modules/backpack" "git.golem15.com/golem15/summercms/modules/cabana" "git.golem15.com/golem15/summercms/modules/pact" "git.golem15.com/golem15/summercms/modules/phrasebook" "gorm.io/gorm" ) // PermissionManage is the one SEO permission (D-15): the SEO tab, URL // rules, redirects, the 404 log, robots.txt, settings and history. const PermissionManage = "golem15.seo.manage" // The admin controller IDs. const ( SettingsControllerID = "golem15.seo.settings" RulesControllerID = "golem15.seo.rules" ) // AdminControllers is the plugin's admin controller list. app returns the // booted application; it is asked on every call, so a controller never // keeps a database handle of its own. func AdminControllers(app func() *backpack.App) []pact.AdminController { return []pact.AdminController{ settingsAdminController{app: app}, redirectsAdminController{app: app}, notFoundAdminController{app: app}, rulesAdminController{app: app}, historyAdminController{app: app}, } } // appOf returns the booted application, nil before boot. func appOf(app func() *backpack.App) *backpack.App { if app == nil { return nil } return app() } // translator returns the application's translator, nil when none. func translator(app func() *backpack.App) *phrasebook.Translator { a := appOf(app) if a == nil { return nil } tr, _ := a.Lookup[*phrasebook.Translator]() return tr } // database returns the write transaction of ctx, else the app database. func database(ctx context.Context, app func() *backpack.App) (*gorm.DB, error) { if tx, ok := cabana.TxFromContext(ctx); ok && tx != nil { return tx, nil } a := appOf(app) if a == nil { return nil, errors.New("golem15.seo: app is not booted") } db, ok := a.Lookup[*gorm.DB]() if !ok || db == nil { return nil, errors.New("golem15.seo: database is not configured") } return db.WithContext(ctx), nil } // Phrase translates key with params in the request locale; without a // translator it returns the key. func Phrase(ctx context.Context, tr *phrasebook.Translator, key string, params map[string]string) string { if tr == nil { return key } return tr.Get(ctx, key, params) } // problemsError turns field problems into a 422 with the messages // translated in the request locale; nil when there are none. func problemsError(ctx context.Context, tr *phrasebook.Translator, problems map[string][]models.Problem) error { if len(problems) == 0 { return nil } details := make(map[string]any, len(problems)) for field, list := range problems { msgs := make([]string, 0, len(list)) for _, p := range list { if len(p.Params) == 0 { msgs = append(msgs, p.Key) continue } msgs = append(msgs, Phrase(ctx, tr, p.Key, p.Params)) } details[field] = msgs } return &cabana.ValidationError{Details: details} }