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:
Jakub Zych
2026-09-30 23:18:35 +02:00
parent f7dfe68707
commit 44bd1446f5
40 changed files with 2882 additions and 30 deletions

View File

@@ -0,0 +1,102 @@
package centrifugo_test
import (
"context"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"git.golem15.com/golem15/summercms/modules/backpack"
"git.golem15.com/golem15/summercms/modules/compass"
"git.golem15.com/golem15/summercms/modules/lighthouse"
"git.golem15.com/golem15/summercms/modules/lighthouse/centrifugo"
"git.golem15.com/golem15/summercms/modules/surf"
)
// newApp returns an app whose config selects a realtime driver.
func newApp(settings map[string]any) (*backpack.App, error) {
cfg, err := compass.Open(compass.Options{Dir: "config", Env: "development", Environ: []string{}})
if err != nil {
return nil, err
}
for k, v := range settings {
if err := cfg.Set(k, v); err != nil {
return nil, err
}
}
return backpack.New(cfg), nil
}
func ExampleProxyHandler() {
svc, err := lighthouse.From(backpack.New(nil))
if err != nil {
fmt.Println(err)
return
}
// Members of blog 7 may subscribe to its channels.
err = svc.Registry().Register("blog", lighthouse.AuthorizerFunc(
func(ctx context.Context, userID uint, channel string) lighthouse.Result {
if userID == 42 && lighthouse.ChannelID(channel) == 7 {
return lighthouse.Allowed(nil)
}
return lighthouse.Denied("not a member of the blog")
}))
if err != nil {
fmt.Println(err)
return
}
// realtime.centrifugo.proxy_secret; set it through the environment.
proxy := centrifugo.ProxyHandler(svc, centrifugo.Config{ProxySecret: "test-only-proxy-secret"})
// What Centrifugo posts to the subscribe proxy.
subscribe := func(secret, user, channel string) {
body := fmt.Sprintf(`{"client":"c1","user":%q,"channel":%q}`, user, channel)
req := httptest.NewRequest(http.MethodPost, "/api/realtime/subscribe", strings.NewReader(body))
req.Header.Set("X-Centrifugo-Secret", secret)
rec := httptest.NewRecorder()
proxy.ServeHTTP(rec, req)
fmt.Println(rec.Code, strings.TrimSpace(rec.Body.String()))
}
subscribe("test-only-proxy-secret", "42", "blog:7")
subscribe("test-only-proxy-secret", "5", "blog:7")
subscribe("wrong-secret", "42", "blog:7")
// Output:
// 200 {"result":{"info":[]}}
// 200 {"error":{"code":403,"message":"Access denied"}}
// 200 {"error":{"code":403,"message":"Access denied"}}
}
func ExampleDriver_Routes() {
app, err := newApp(map[string]any{"realtime.driver": "centrifugo"})
if err != nil {
fmt.Println(err)
return
}
svc, err := lighthouse.From(app)
if err != nil {
fmt.Println(err)
return
}
// In a plugin's Routes method, r is the router the plugin receives.
r := surf.New(nil)
err = lighthouse.Mount(r, svc.Driver(), lighthouse.Surfaces{
UserAuth: surf.Use("acme.auth"),
Middleware: surf.Use("throttle:60,1"),
})
if err != nil {
fmt.Println(err)
return
}
for _, rt := range r.Routes() {
fmt.Println(rt.Method, rt.Pattern, rt.Middleware, "raw:", rt.Raw)
}
// A user route without a guard is refused.
err = lighthouse.Mount(surf.New(nil), svc.Driver(), lighthouse.Surfaces{})
fmt.Println(err != nil)
// Output:
// GET /api/realtime/token [acme.auth throttle:60,1] raw: false
// POST /api/realtime/subscribe [throttle:60,1] raw: true
// true
}