feat: sitemap settings singleton with content types and additional addresses
- one settings tile opening the golem15.sitemap.settings singleton - Files tab (site address, file size, S1 files partial), Additional addresses repeater, Content types tab built from signpost at boot - golem15_sitemap_types; a type switched off leaves the files (reason type) - a save validates (errors.*) and dispatches a rebuild after commit
This commit is contained in:
@@ -2,6 +2,7 @@ package models
|
||||
|
||||
import (
|
||||
"net/url"
|
||||
"strconv"
|
||||
"strings"
|
||||
"time"
|
||||
"unicode"
|
||||
@@ -9,7 +10,6 @@ import (
|
||||
|
||||
"git.golem15.com/golem15/summercms/modules/cabana"
|
||||
"git.golem15.com/golem15/summercms/modules/lagoon"
|
||||
"gorm.io/gorm"
|
||||
)
|
||||
|
||||
// errFrontendURL is the phrase key of the frontend_url refusal.
|
||||
@@ -95,28 +95,62 @@ func (s Settings) Extra() []ExtraURL { return s.ExtraURLs.Get() }
|
||||
|
||||
func (Settings) TableName() string { return "golem15_sitemap_settings" }
|
||||
|
||||
// Fillable lists only the settings columns.
|
||||
// Fillable lists the columns the settings form edits.
|
||||
func (Settings) Fillable() []string {
|
||||
return []string{
|
||||
"frontend_url", "frontend_default_locale", "updates_manifest_path",
|
||||
"enable_priority_tiers", PriorityHomepage, PriorityServices,
|
||||
PriorityBlog, PriorityCategories, PriorityLegal,
|
||||
}
|
||||
return []string{"site_url", "file_size", "extra_urls"}
|
||||
}
|
||||
|
||||
// Rules is empty: the PHP settings form validated nothing. The one check of
|
||||
// the port, on frontend_url, is in BeforeValidate.
|
||||
func (Settings) Rules() map[string]string { return map[string]string{} }
|
||||
|
||||
// BeforeValidate is the lagoon.HasBeforeValidate hook cabana calls on a
|
||||
// settings save, after the fill and before the row is written: a
|
||||
// frontend_url that AllowedFrontendURL refuses is a validation error on
|
||||
// frontend_url, so the save answers 422 and the row is left as it was.
|
||||
func (s Settings) BeforeValidate(*gorm.DB) error {
|
||||
if AllowedFrontendURL(Text(s.FrontendURL)) {
|
||||
// The phrase keys of the other settings refusals.
|
||||
const (
|
||||
errFileSize = "golem15.sitemap::lang.errors.file_size"
|
||||
errURLScheme = "golem15.sitemap::lang.errors.url_scheme"
|
||||
errLastmod = "golem15.sitemap::lang.errors.lastmod"
|
||||
)
|
||||
|
||||
// Validate answers every problem Problems finds as a validation error on
|
||||
// its field; the settings controller calls it before a save, so the save
|
||||
// answers 422 and the row is left as it was.
|
||||
func (s Settings) Validate() error {
|
||||
problems := s.Problems()
|
||||
if len(problems) == 0 {
|
||||
return nil
|
||||
}
|
||||
return &cabana.ValidationError{Details: map[string]any{"frontend_url": []string{errFrontendURL}}}
|
||||
details := make(map[string]any, len(problems))
|
||||
for field, keys := range problems {
|
||||
details[field] = keys
|
||||
}
|
||||
return &cabana.ValidationError{Details: details}
|
||||
}
|
||||
|
||||
// Problems lists the settings' refusals by field (UI-SPEC C26): a site
|
||||
// address that AllowedFrontendURL refuses (site_url and the Phase 17
|
||||
// frontend_url), a file size outside 1..MaxFileSize and an additional
|
||||
// address that is not a path or an http(s) URL (extra_urls.<i>.url) or
|
||||
// whose lastmod is not a date (extra_urls.<i>.lastmod).
|
||||
func (s Settings) Problems() map[string][]string {
|
||||
out := map[string][]string{}
|
||||
if !AllowedFrontendURL(Text(s.SiteURL)) {
|
||||
out["site_url"] = []string{errFrontendURL}
|
||||
}
|
||||
if !AllowedFrontendURL(Text(s.FrontendURL)) {
|
||||
out["frontend_url"] = []string{errFrontendURL}
|
||||
}
|
||||
if s.FileSize < 1 || s.FileSize > MaxFileSize {
|
||||
out["file_size"] = []string{errFileSize}
|
||||
}
|
||||
for i, e := range s.Extra() {
|
||||
if !AllowedAddress(strings.TrimSpace(e.URL)) {
|
||||
out["extra_urls."+strconv.Itoa(i)+".url"] = []string{errURLScheme}
|
||||
}
|
||||
if mod := strings.TrimSpace(e.LastMod); mod != "" && !ValidDate(mod) {
|
||||
out["extra_urls."+strconv.Itoa(i)+".lastmod"] = []string{errLastmod}
|
||||
}
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// AllowedFrontendURL reports whether u may be the frontend_url setting: the
|
||||
@@ -180,3 +214,26 @@ func (s Settings) Priority(key string) string {
|
||||
func init() {
|
||||
Register(Settings{})
|
||||
}
|
||||
|
||||
// dateLayouts are the lastmod forms an additional address may carry.
|
||||
var dateLayouts = []string{time.RFC3339Nano, "2006-01-02 15:04:05", "2006-01-02T15:04:05", "2006-01-02"}
|
||||
|
||||
// ParseDate reads a lastmod as the settings form or an import gives it
|
||||
// (YYYY-MM-DD, "YYYY-MM-DD hh:mm:ss" or RFC 3339; UTC unless it names a
|
||||
// zone); nil when empty or unreadable.
|
||||
func ParseDate(s string) *time.Time {
|
||||
s = strings.TrimSpace(s)
|
||||
if s == "" {
|
||||
return nil
|
||||
}
|
||||
for _, layout := range dateLayouts {
|
||||
if t, err := time.Parse(layout, s); err == nil {
|
||||
t = t.UTC()
|
||||
return &t
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// ValidDate reports whether s is a lastmod ParseDate reads.
|
||||
func ValidDate(s string) bool { return ParseDate(s) != nil }
|
||||
|
||||
Reference in New Issue
Block a user