feat: sitemap index and per-type files from a slot table (tracer)

- contract package: Filter, Listing and their types (D-95)
- golem15_sitemap_urls slot table and the new settings columns
- Rebuild over signpost Records, stable slot assignment, rebuild job
- GET /sitemap.xml index and GET /_sitemap/files/{file} from the table
This commit is contained in:
Jakub Zych
2026-11-04 10:58:05 +01:00
parent 049f3ee270
commit 3359f1a196
17 changed files with 1564 additions and 577 deletions

View File

@@ -1,16 +1,15 @@
// Package sitemap is the golem15.sitemap plugin: it serves /sitemap.xml
// from the application's one sitemap definition, byte for byte the way the
// WinterCMS Golem15.Sitemap plugin did, with items that are explicit URLs
// or menu item types of the framework's registry.
// Package sitemap is the golem15.sitemap plugin: it serves a sitemap index
// at /sitemap.xml whose entries are per-group files of a configurable size
// (sitemap-<slug>-<n>.xml, D-30), built from the published records of every
// signpost content type and the additional addresses of its settings, with
// stable file numbers and real lastmod. Other plugins narrow it through the
// Filter of the contract package and read it through its Listing.
package sitemap
import (
"embed"
"fmt"
"io/fs"
"time"
"git.golem15.com/golem15/sm-sitemap-plugin/classes"
"git.golem15.com/golem15/sm-sitemap-plugin/console"
"git.golem15.com/golem15/sm-sitemap-plugin/models"
"git.golem15.com/golem15/sm-sitemap-plugin/updates"
@@ -29,28 +28,22 @@ var (
_ pact.HasModels = (*Plugin)(nil)
_ pact.HasRoutes = (*Plugin)(nil)
_ pact.HasCommands = (*Plugin)(nil)
_ pact.HasJobs = (*Plugin)(nil)
_ surf.BucketProvider = (*Plugin)(nil)
)
//go:embed config
var configFS embed.FS
// sitemapXSL is the PHP plugin's assets/sitemap.xsl, byte for byte.
//
//go:embed assets/sitemap.xsl
var sitemapXSL []byte
// Plugin is the golem15.sitemap plugin.
type Plugin struct {
app *backpack.App
tiers classes.Tiers
cache *sitemapCache
app *backpack.App
}
func (p *Plugin) ID() string { return "golem15.sitemap" }
// Requires is empty: the sitemap reads content types through the framework's
// menu item type registry, not from a named plugin.
// Requires is empty: the sitemap reads content types through signpost, not
// from a named plugin, and a Filter through the service registry.
func (p *Plugin) Requires() []string { return nil }
func (p *Plugin) Register(app *backpack.App) error {
@@ -58,29 +51,9 @@ func (p *Plugin) Register(app *backpack.App) error {
return nil
}
// Boot compiles the priority tier rules (classes.TiersConfigKey); a rule
// with an unknown setting or a pattern that does not compile fails the boot.
// It also starts an empty sitemap cache for CacheConfigKey seconds; a
// negative value fails the boot.
// Boot keeps the booted application for the routes, jobs and commands.
func (p *Plugin) Boot(app *backpack.App) error {
p.app = app
var tiers classes.Tiers
var err error
seconds := 0
if app != nil {
tiers, err = classes.LoadTiers(app.Config)
if app.Config != nil {
seconds = app.Config.Int(CacheConfigKey)
}
}
if err != nil {
return fmt.Errorf("golem15.sitemap: %w", err)
}
if seconds < 0 {
return fmt.Errorf("golem15.sitemap: %s must not be negative, got %d", CacheConfigKey, seconds)
}
p.tiers = tiers
p.cache = newSitemapCache(time.Duration(seconds) * time.Second)
return nil
}