feat(cabana): settings entries that link to an admin controller

pact.SettingsItem gains an additive Controller field, the equivalent of a
WinterCMS registerSettings 'url' => Backend::url(...) entry. A link entry
declares no Model, Form or NewModel and needs no AdminFS; start-up fails
when it combines Controller with a singleton form or a model, or names an
unregistered controller. Settings codes stay one namespace.

GET /settings lists a link entry with its controller only when the
principal passes the item's permissions and may open the controller.
Registry.Setting never returns a link entry, so the singleton settings
endpoints answer 404 for its code. SettingsEntry carries controller,
empty for singletons; the OpenAPI document, generated SPA types and
settings fixture follow, and the pact and cabana READMEs and the
settings docs describe the link.
This commit is contained in:
Jakub Zych
2026-10-07 18:22:01 +02:00
parent b040ee3a74
commit 4a9b0896b1
15 changed files with 338 additions and 14 deletions

View File

@@ -17,7 +17,8 @@ import (
)
// CompiledSetting is a singleton settings registration after strict schema
// compilation and writable-field binding.
// compilation and writable-field binding. For a link entry (Item.Controller
// set) Form and Writable are nil.
type CompiledSetting struct {
PluginID string
Item pact.SettingsItem
@@ -34,6 +35,19 @@ type SettingsResult struct {
// SettingsService reads and transactionally updates compiled singleton rows.
type SettingsService struct{ DB *gorm.DB }
// compileSettingLink registers a settings entry that opens an admin
// controller. It declares no singleton form and no model; the controller ID
// itself is checked against the registry once every plugin is collected.
func compileSettingLink(pluginID string, item pact.SettingsItem) (*CompiledSetting, error) {
if item.Form != "" || item.NewModel != nil {
return nil, fmt.Errorf("cabana: setting %s links controller %s and declares a singleton form; declare one", item.Code, item.Controller)
}
if item.Model != "" {
return nil, fmt.Errorf("cabana: setting %s links controller %s and names a model", item.Code, item.Controller)
}
return &CompiledSetting{PluginID: pluginID, Item: item}, nil
}
func compileSetting(pluginID string, item pact.SettingsItem, fsys fs.FS) (*CompiledSetting, error) {
if item.NewModel == nil {
return nil, fmt.Errorf("cabana: setting %s has no model factory", item.Code)