Files
Jakub Zych 1c5491232a feat(17-04): FAQ plugin tracer, admin FAQ with sanitized question repeater
- golem15.faq plugin (Requires golem15.translate), table golem15_faq_faqs with the PHP columns
- FAQ model with jsonable content and the translate Locale relation
- admin controller golem15.faq.faqs: name, slug, locale, active switch, richeditor description, question repeater
- navigation FAQ (order 500), permission golem15.faq.faqs, en and pl phrasebooks
- TestFaqAdminSmoke: answers stored sanitized with a notice under content.1.answer
2026-10-12 17:24:52 +02:00

80 lines
2.7 KiB
Go

package controllers
import (
"context"
"git.golem15.com/golem15/sm-faq-plugin/models"
translate "git.golem15.com/golem15/sm-translate-plugin/models"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/cabana"
"git.golem15.com/golem15/summercms/modules/pact"
)
const (
// FAQsControllerID is the cabana controller id of the FAQs screen.
FAQsControllerID = "golem15.faq.faqs"
// PermissionFAQs is the PHP permission golem15.faq.faqs.
PermissionFAQs = "golem15.faq.faqs"
)
// faqsAdminController lists and edits FAQs: a name, a slug, the locale, the
// active switch, a rich text description and the question repeater.
type faqsAdminController struct {
app func() *backpack.App
}
func (faqsAdminController) ID() string { return FAQsControllerID }
func (faqsAdminController) ModelName() string { return `Golem15\FAQ\Models\FAQ` }
func (faqsAdminController) ConfigDir() string { return "controllers/faqs" }
func (faqsAdminController) RequiredPermissions() []string {
return []string{PermissionFAQs}
}
func (faqsAdminController) NewRecord() any { return &models.FAQ{} }
// AdminFieldRelations binds the locale field to the translate Locale through
// locale_id (PHP belongsTo locale).
func (faqsAdminController) AdminFieldRelations() []cabana.FieldRelationContract {
return []cabana.FieldRelationContract{{
Field: "locale",
Kind: "belongsTo",
NewRelated: func() any { return &translate.Locale{} },
ForeignKey: "locale_id",
}}
}
// errLocaleRequired is the field message of a FAQ saved without a locale.
const errLocaleRequired = "golem15.faq::lang.errors.locale_required"
// FormAfterCreate refuses a FAQ without a locale. cabana writes locale_id
// from the relation field after the model rules ran, so the check runs here,
// inside the save's transaction: the refusal rolls the row back.
func (faqsAdminController) FormAfterCreate(_ context.Context, model any) error {
return requireLocale(model)
}
// FormAfterUpdate applies the same check to an update.
func (faqsAdminController) FormAfterUpdate(_ context.Context, model any) error {
return requireLocale(model)
}
func requireLocale(model any) error {
faq, ok := model.(*models.FAQ)
if !ok || faq == nil || faq.LocaleID != 0 {
return nil
}
return &cabana.ValidationError{Details: map[string]any{"locale": []string{errLocaleRequired}}}
}
var (
_ pact.FormAfterCreate = faqsAdminController{}
_ pact.FormAfterUpdate = faqsAdminController{}
_ pact.AdminController = faqsAdminController{}
_ pact.AdminPermissioned = faqsAdminController{}
_ pact.AdminRecordSource = faqsAdminController{}
_ cabana.FieldRelationProvider = faqsAdminController{}
)