feat(11.1-04): add the Backend section, the remaining Services pages and the concept map links
- docs/backend: admin controllers, forms, lists and filters, relation manager, users and permissions, settings, partials and widgets, admin SPA - docs/services: storage, outbound HTTP, realtime, Web Push, search, parity testing and the Frontend and AJAX (not provided) page - Examples for cabana (with testdata/docs YAML), fetchguard, lighthouse and its centrifugo driver, flare, beachcomber and typesense, tide; lighthouse and beachcomber TestDocs* regions run on their Postgres harnesses - concept map rows link their guide pages and the not-provided rows the Frontend and AJAX page; index lists Backend, Database and Services - TestDocsRequiredPages asserts the D-08 section order
This commit is contained in:
85
docs/backend/settings.md
Normal file
85
docs/backend/settings.md
Normal file
@@ -0,0 +1,85 @@
|
||||
---
|
||||
title: Settings
|
||||
description: Declare singleton settings pages with pact.SettingsItem, backed by a model, a fields.yaml form and validation rules, and read them from plugin code.
|
||||
section: backend
|
||||
order: 60
|
||||
---
|
||||
# Settings
|
||||
|
||||
A WinterCMS settings model extends `SettingModel`, stores its values in `system_settings` and registers its page with `registerSettings`. In SummerCMS a settings page is a singleton row of the plugin's own table, edited through a form described in `fields.yaml`, and declared with `pact.HasSettings`.
|
||||
|
||||
## Declaring a settings page
|
||||
|
||||
`pact.HasSettings` returns `pact.SettingsItem` entries. Each names the page's code, label, category and icon for the settings index, the permissions it needs, the form file inside the plugin's embedded tree, and a factory for its model:
|
||||
|
||||
```go src=modules/cabana/example_controller_test.go#BlogPlugin.Settings
|
||||
// Settings replaces registerSettings().
|
||||
func (p *BlogPlugin) Settings() []pact.SettingsItem {
|
||||
return []pact.SettingsItem{{
|
||||
Code: "blog",
|
||||
Label: "acme.blog::lang.settings.label",
|
||||
Description: "acme.blog::lang.settings.description",
|
||||
Category: "acme.blog::lang.plugin.name",
|
||||
Icon: "icon-pencil",
|
||||
Model: "BlogSettings",
|
||||
Permissions: []string{"acme.blog.access_settings"},
|
||||
Form: "models/settings/fields.yaml",
|
||||
NewModel: func() any { return &BlogSettings{} },
|
||||
}}
|
||||
}
|
||||
```
|
||||
|
||||
The model is a GORM struct with a `Fillable` list and a `Rules` method; [cabana](../../modules/cabana/README.md) refuses at start-up a settings model without either:
|
||||
|
||||
```go src=modules/cabana/example_controller_test.go#BlogSettings
|
||||
// BlogSettings is the singleton row behind the plugin's settings page.
|
||||
type BlogSettings struct {
|
||||
ID uint `gorm:"column:id;primaryKey"`
|
||||
PostsPerPage int `gorm:"column:posts_per_page"`
|
||||
CommentsEnabled bool `gorm:"column:comments_enabled"`
|
||||
}
|
||||
```
|
||||
|
||||
```go src=modules/cabana/example_controller_test.go#BlogSettings.Rules
|
||||
// Rules validates a settings save, as a WinterCMS settings model's $rules.
|
||||
func (BlogSettings) Rules() map[string]string {
|
||||
return map[string]string{"posts_per_page": "required|integer|between:1,100"}
|
||||
}
|
||||
```
|
||||
|
||||
The form uses the same field types and options as a controller form; see [Forms](forms.md):
|
||||
|
||||
```yaml src=modules/cabana/testdata/docs/models/settings/fields.yaml
|
||||
fields:
|
||||
posts_per_page:
|
||||
label: acme.blog::lang.settings.posts_per_page
|
||||
type: number
|
||||
span: left
|
||||
default: 10
|
||||
comments_enabled:
|
||||
label: acme.blog::lang.settings.comments_enabled
|
||||
type: switch
|
||||
span: right
|
||||
```
|
||||
|
||||
Create the table in a migration like any other; see [Migrations](../database/migrations.md).
|
||||
|
||||
## How values are stored
|
||||
|
||||
The settings row is the one with ID 1. Before it exists, the page shows the fields' `default` values and the API reports that the row does not exist yet; the first save creates it. A save writes only fillable fields, validates them with the model's rules and the form's `required` flags, and runs in a transaction, as a controller form save does.
|
||||
|
||||
The admin API serves the page at `<prefix>/api/v1/settings/{code}` (values) and `.../settings/{code}/schema` (the form), and `GET .../settings` lists the pages the administrator may open.
|
||||
|
||||
## Reading settings in plugin code
|
||||
|
||||
Settings are an ordinary table, so plugin code reads them with GORM:
|
||||
|
||||
```go src=modules/beachcomber/example_test.go#gate
|
||||
svc.SetGate(beachcomber.GateFunc(func(ctx context.Context, db *gorm.DB) bool {
|
||||
var enabled bool
|
||||
err := db.WithContext(ctx).Raw(`SELECT search_enabled FROM acme_blog_settings WHERE id = 1`).Scan(&enabled).Error
|
||||
return err == nil && enabled
|
||||
}))
|
||||
```
|
||||
|
||||
That example reads a flag from a settings row inside a search gate, treating a failed read as off. A setting that changes rarely and that operators, not administrators, own belongs in configuration instead; see [Configuration](../services/configuration.md).
|
||||
Reference in New Issue
Block a user