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,112 @@
package flare_test
import (
"context"
"crypto/ecdh"
"crypto/rand"
"encoding/base64"
"errors"
"fmt"
"net/http"
"net/http/httptest"
"strings"
"time"
"git.golem15.com/golem15/summercms/modules/flare"
)
// browserSubscription stands in for the PushSubscription a browser posts to
// the application: an endpoint and the subscriber's p256dh and auth keys.
func browserSubscription(endpoint string) (flare.Subscription, error) {
key, err := ecdh.P256().GenerateKey(rand.Reader)
if err != nil {
return flare.Subscription{}, err
}
auth := make([]byte, 16)
if _, err := rand.Read(auth); err != nil {
return flare.Subscription{}, err
}
enc := base64.RawURLEncoding
return flare.Subscription{
Endpoint: endpoint,
P256dh: enc.EncodeToString(key.PublicKey().Bytes()),
Auth: enc.EncodeToString(auth),
}, nil
}
func ExampleVAPIDPusher_Send() {
// A stand-in push service: 201 for a live subscription, 410 for one the
// browser dropped.
push := httptest.NewTLSServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
if r.URL.Path == "/gone" {
w.WriteHeader(http.StatusGone)
return
}
fmt.Println("push service got", r.Header.Get("Content-Encoding"), r.Header.Get("TTL"), r.Header.Get("Urgency"))
w.WriteHeader(http.StatusCreated)
}))
defer push.Close()
keys, err := flare.GenerateVAPIDKeys() // websockets:generate-vapid-keys
if err != nil {
fmt.Println(err)
return
}
cfg := flare.Config{
Enabled: true,
PublicKey: keys.PublicKey,
PrivateKey: keys.PrivateKey,
Subject: "mailto:admin@example.com",
TTL: time.Hour,
AllowedHosts: []string{"127.0.0.1"}, // production keeps the default push services
}
// The test server's client trusts its certificate.
pusher := flare.NewVAPIDPusher(cfg, push.Client())
ctx := context.Background()
payload := []byte(`{"title":"New comment","body":"Someone replied to your post"}`)
for _, endpoint := range []string{
push.URL + "/live",
push.URL + "/gone",
"https://push.attacker.example/steal",
"http://127.0.0.1/plain",
} {
sub, err := browserSubscription(endpoint)
if err != nil {
fmt.Println(err)
return
}
err = pusher.Send(ctx, sub, payload, flare.SendOptions{Urgency: "normal"})
switch {
case err == nil:
fmt.Println("sent")
case errors.Is(err, flare.ErrSubscriptionGone):
fmt.Println("gone: delete the subscription")
case errors.Is(err, flare.ErrEndpointNotAllowed):
fmt.Println("refused before connecting")
default:
fmt.Println("error:", err)
}
}
// Formatting the keys never prints the private key.
fmt.Println(strings.Contains(fmt.Sprintf("%v %#v", keys, keys), keys.PrivateKey))
// Output:
// push service got aes128gcm 3600 normal
// sent
// gone: delete the subscription
// refused before connecting
// refused before connecting
// false
}
func ExampleHostAllowed() {
allowed := []string{"fcm.googleapis.com", "*.push.apple.com"}
for _, host := range []string{"fcm.googleapis.com", "api.push.apple.com", "push.apple.com", "evil.example"} {
fmt.Println(host, flare.HostAllowed(host, allowed))
}
// Output:
// fcm.googleapis.com true
// api.push.apple.com true
// push.apple.com false
// evil.example false
}