Files
sm-sitemap-plugin/models/url.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

77 lines
2.9 KiB
Go

package models
import (
"strings"
"time"
"unicode"
"unicode/utf8"
"git.golem15.com/golem15/summercms/modules/lagoon"
)
// ExtraTypeKey is the type_key of the settings' additional addresses in
// golem15_sitemap_urls; their record_id is the hex SHA-256 of the address.
const ExtraTypeKey = "extra"
// ExtraGroup is the file group of the additional addresses.
const ExtraGroup = "pages"
// The excluded reasons the sitemap sets itself. A Filter's reasons
// (contract.ReasonNoindex and the others) are stored as it gives them.
const (
// ReasonDeleted is an address whose record or locale is gone.
ReasonDeleted = "deleted"
// ReasonUnpublished is an address whose record is not published.
ReasonUnpublished = "unpublished"
)
// Alternate is one hreflang alternate of an address: Hreflang the locale
// code or x-default, Href the alternate's path or absolute URL.
type Alternate struct {
Hreflang string `json:"hreflang"`
Href string `json:"href"`
}
// URL is one row of the slot table golem15_sitemap_urls (D-30): an address
// of a content type's record in one locale, or an additional address. Grp
// and FileNo place it in the file sitemap-<grp>-<file_no>.xml; FileNo is
// nil until the address is numbered. ExcludedReason is "" for a listed
// address; a row left out keeps its FileNo, so its file keeps a hole.
type URL struct {
ID uint64 `gorm:"column:id;primaryKey"`
Grp string `gorm:"column:grp"`
TypeKey string `gorm:"column:type_key"`
RecordID string `gorm:"column:record_id"`
Locale string `gorm:"column:locale"`
URL string `gorm:"column:url"`
Title string `gorm:"column:title"`
FileNo *int `gorm:"column:file_no"`
FirstSeen time.Time `gorm:"column:first_seen"`
LastMod *time.Time `gorm:"column:lastmod"`
Alternates lagoon.Jsonable[[]Alternate] `gorm:"column:alternates"`
Images lagoon.Jsonable[[]string] `gorm:"column:images"`
ExcludedReason string `gorm:"column:excluded_reason"`
UpdatedAt time.Time `gorm:"column:updated_at"`
}
func (URL) TableName() string { return "golem15_sitemap_urls" }
func init() {
Register(URL{})
}
// AllowedAddress reports whether u may be a stored address: a path that
// starts with a single / (no whitespace, control characters, quotes, < or
// >), or an absolute http(s) URL that AllowedFrontendURL accepts.
func AllowedAddress(u string) bool {
if u == "" || !utf8.ValidString(u) {
return false
}
if strings.HasPrefix(u, "/") && !strings.HasPrefix(u, "//") {
return !strings.ContainsFunc(u, func(r rune) bool {
return unicode.IsControl(r) || unicode.IsSpace(r) || r == '"' || r == '<' || r == '>' || r == '\\'
})
}
return AllowedFrontendURL(u)
}