feat(10.1-01): run registered widget actions through a cabana-owned route

- pact: AdminClientAssets, AdminAction, AdminActionInput, AdminActionResult,
  HasAdminActions and AdminPartialData contracts
- fields.yaml type: widget with widget, action and fill keys; boot checks the
  plugin tag prefix, the registered action and writable scalar fill fields
- POST .../widgets/{field} behind requireAjax, controller and action
  permissions, scoped non-locking record read and a server-side fill filter
- typed OpenAPI operation, inventories and an acme conformance case
This commit is contained in:
Jakub Zych
2026-09-28 23:35:00 +02:00
parent 9b98d8409f
commit f9281949a6
19 changed files with 876 additions and 10 deletions

View File

@@ -6,6 +6,7 @@ import (
"fmt"
"reflect"
"sort"
"strings"
"git.golem15.com/golem15/summercms/modules/phrasebook"
)
@@ -240,6 +241,18 @@ func validateMessageKeys(reg *Registry, tr *phrasebook.Translator) error {
return err
}
}
actionNames := make([]string, 0, len(cc.Actions))
for name := range cc.Actions {
actionNames = append(actionNames, name)
}
sort.Strings(actionNames)
for _, name := range actionNames {
// A label with a namespace separator is a phrase key and must
// resolve; anything else is literal button text.
if label := cc.Actions[name].Label; strings.Contains(label, "::") && !tr.Has(label) {
return fmt.Errorf("cabana: admin controller %s/%s: action %s label names missing phrase key %s", cc.PluginID, id, name, label)
}
}
names := make([]string, 0, len(cc.Relations))
for name := range cc.Relations {
names = append(names, name)