Files
sm-sitemap-plugin/contract/contract.go
Jakub Zych 3359f1a196 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
2026-11-04 10:58:05 +01:00

149 lines
5.3 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)
}
// 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), HTMLPageSize and KeyFile (the IndexNow key file name) describe the
// HTML sitemap and IndexNow; they stay zero until those settings exist.
type ListingSettings struct {
SiteURL string
IndexURL string
DefaultLocale string
Groups []GroupInfo
HTMLEnabled bool
HTMLPaths map[string]string
HTMLPageSize int
KeyFile 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
}