The sitemap publishes classes.Listing as contract.Rebuilder too, so a plugin whose Filter reads data of its own can queue the debounced rebuild a record event queues when that data changes.
166 lines
6.2 KiB
Go
166 lines
6.2 KiB
Go
// Package contract is the published seam of the golem15.sitemap plugin:
|
|
// the Filter another plugin implements to keep addresses out of the
|
|
// sitemaps or move them to another file group, and the Listing other
|
|
// plugins read to show the sitemap's addresses (an HTML sitemap, IndexNow).
|
|
//
|
|
// It is a package of its own so a plugin can import it without importing
|
|
// the sitemap plugin's root package, whose init registers the plugin. The
|
|
// sitemap plugin depends on no implementation: it looks a Filter up with
|
|
//
|
|
// f, ok := app.Lookup[contract.Filter]()
|
|
//
|
|
// and includes every published address when none is published. A plugin
|
|
// publishes one in its Register:
|
|
//
|
|
// app.Publish[contract.Filter](myFilter{})
|
|
package contract
|
|
|
|
import (
|
|
"context"
|
|
"time"
|
|
)
|
|
|
|
// The reasons a Decision may give for leaving an address out. The sitemap
|
|
// counts excluded addresses by reason; another reason is counted under its
|
|
// own name.
|
|
const (
|
|
// ReasonNoindex is an address whose page is set to noindex.
|
|
ReasonNoindex = "noindex"
|
|
// ReasonCanonical is an address whose canonical URL points elsewhere.
|
|
ReasonCanonical = "canonical"
|
|
// ReasonRedirect is an address that redirects.
|
|
ReasonRedirect = "redirect"
|
|
// ReasonType is an address of a content type switched off in the
|
|
// sitemap settings; the sitemap sets it itself, a Filter need not.
|
|
ReasonType = "type"
|
|
)
|
|
|
|
// Candidate is one address the sitemap would list: TypeKey is the content
|
|
// type key (a signpost menu item type, or "extra" for an additional address
|
|
// of the settings), RecordID the record's ID within it (for an additional
|
|
// address the hex SHA-256 of its path), Locale the locale ("" when the
|
|
// address has none) and URL its public path or absolute URL.
|
|
type Candidate struct {
|
|
TypeKey string
|
|
RecordID string
|
|
Locale string
|
|
URL string
|
|
}
|
|
|
|
// Decision is a Filter's answer for one Candidate. Exclude leaves the
|
|
// address out of the sitemap files, counted under Reason. Group, when not
|
|
// empty, puts the address in another file group: the files are then named
|
|
// sitemap-<group>-<n>.xml, so a Group must use lowercase letters, digits and
|
|
// hyphens (another value is ignored). LastMod, when set, is the first
|
|
// lastmod of an address the sitemap has not listed before; it never moves
|
|
// the lastmod of a listed address.
|
|
type Decision struct {
|
|
Exclude bool
|
|
Reason string
|
|
Group string
|
|
LastMod *time.Time
|
|
}
|
|
|
|
// Group names a file group a Filter's decisions use: Key is the group as
|
|
// in Decision.Group, Titles its heading per locale for an HTML sitemap.
|
|
type Group struct {
|
|
Key string
|
|
Titles map[string]string
|
|
}
|
|
|
|
// Filter decides which addresses the sitemap lists. Decide receives one
|
|
// batch (a page of records of one content type, or the additional
|
|
// addresses) and answers one Decision per Candidate in the same order; an
|
|
// answer of another length or an error fails the rebuild, which keeps the
|
|
// files it served before. Groups lists the groups the Filter's decisions
|
|
// may name, with their titles.
|
|
type Filter interface {
|
|
Decide(ctx context.Context, batch []Candidate) ([]Decision, error)
|
|
Groups(ctx context.Context) ([]Group, error)
|
|
}
|
|
|
|
// Rebuilder asks the sitemap for a rebuild. RequestRebuild queues one
|
|
// debounced rebuild, the same one a record event queues: a request inside
|
|
// the debounce window joins the rebuild already queued. Another plugin
|
|
// calls it when something its Filter reads has changed (the SEO plugin's
|
|
// noindex paths). The sitemap plugin publishes it with
|
|
// app.Publish[contract.Rebuilder] in its Register; a plugin that finds none
|
|
// published (an older sitemap plugin) leaves the change to the next
|
|
// scheduled rebuild.
|
|
type Rebuilder interface {
|
|
RequestRebuild(ctx context.Context) error
|
|
}
|
|
|
|
// Listing is what the sitemap publishes for other plugins: its settings and
|
|
// the listed addresses of each group. The sitemap plugin publishes it with
|
|
// app.Publish[contract.Listing] in its Register.
|
|
type Listing interface {
|
|
Settings(ctx context.Context) (ListingSettings, error)
|
|
Entries(ctx context.Context, q EntriesQuery) (EntriesPage, error)
|
|
}
|
|
|
|
// ListingSettings are the sitemap's settings as other plugins need them.
|
|
// SiteURL is the absolute site address every address starts with ("" when
|
|
// none is configured), IndexURL the absolute address of the sitemap index,
|
|
// DefaultLocale the site's default locale ("" when not set) and Groups the
|
|
// file groups with listed addresses. HTMLEnabled, HTMLPaths (locale to
|
|
// path) and HTMLPageSize are the HTML sitemap's settings (the sitemap
|
|
// serves its data at /_sitemap/api/v1/html); KeyFile is the IndexNow key
|
|
// file name (<key>.txt, served at /_sitemap/indexnow/<key>.txt for the
|
|
// site root), "" while IndexNow is off. TypesOff lists the content types
|
|
// (signpost keys) switched off in the sitemap settings, whose addresses
|
|
// are left out with ReasonType.
|
|
type ListingSettings struct {
|
|
SiteURL string
|
|
IndexURL string
|
|
DefaultLocale string
|
|
Groups []GroupInfo
|
|
HTMLEnabled bool
|
|
HTMLPaths map[string]string
|
|
HTMLPageSize int
|
|
KeyFile string
|
|
TypesOff []string
|
|
}
|
|
|
|
// GroupInfo is one file group: Key is the group (as Entries takes it), Slug
|
|
// the name part of its files (sitemap-<slug>-<n>.xml; today the same as
|
|
// Key), Titles its heading per locale and Count its listed addresses.
|
|
type GroupInfo struct {
|
|
Key string
|
|
Slug string
|
|
Titles map[string]string
|
|
Count int
|
|
}
|
|
|
|
// EntriesQuery asks for one page of a group's listed addresses. Locale,
|
|
// when set, keeps the addresses of that locale and those without one.
|
|
// After is the Next of the previous page ("" for the first) and Limit the
|
|
// page size (at most 1000; 0 means 100).
|
|
type EntriesQuery struct {
|
|
Group string
|
|
Locale string
|
|
After string
|
|
Limit int
|
|
}
|
|
|
|
// Entry is one listed address. URL is absolute (on ListingSettings.SiteURL)
|
|
// and LastMod its last real change, nil when unknown.
|
|
type Entry struct {
|
|
Group string
|
|
TypeKey string
|
|
RecordID string
|
|
Locale string
|
|
URL string
|
|
Title string
|
|
LastMod *time.Time
|
|
}
|
|
|
|
// EntriesPage is one page of Entries in a stable order: Next is the After
|
|
// of the following page ("" on the last page) and Total the number of
|
|
// addresses the query matches across all pages.
|
|
type EntriesPage struct {
|
|
Entries []Entry
|
|
Next string
|
|
Total int
|
|
}
|