feat: IndexNow key, key file and pings

- saving with IndexNow on generates a 32-hex key (crypto/rand);
  indexnow_new_key generates a new one, so the old key file answers 404
- GET /_sitemap/indexnow/{file} serves <key>.txt as text/plain while
  IndexNow is on; the SEO layer maps it to the site root
- after each rebuild in production the added, changed and removed
  addresses on the site host are queued as golem15.sitemap.indexnow jobs
  (at most 10 000 per request, five attempts)
- the job posts {host, key, urlList} to api.indexnow.org through a
  fetchguard client limited to that host and records the answer; 429 and
  5xx retry, 400, 403 and 422 do not
- the IndexNow tab shows the S2 status partial: key or no key, last
  notification or never, and the 403, 422 and 429 callouts
This commit is contained in:
Jakub Zych
2026-11-04 12:26:47 +01:00
parent a10ce2d9de
commit e02d0b173f
13 changed files with 780 additions and 18 deletions

View File

@@ -92,7 +92,7 @@ func (Settings) TableName() string { return "golem15_sitemap_settings" }
// Fillable lists the columns the settings form edits.
func (Settings) Fillable() []string {
return []string{"site_url", "file_size", "extra_urls", "images_enabled", "news_enabled", "news_publication", "news_sources"}
return []string{"site_url", "file_size", "extra_urls", "images_enabled", "news_enabled", "news_publication", "news_sources", "indexnow_enabled"}
}
// Rules is empty: the settings controller runs Validate before a save.

View File

@@ -99,3 +99,31 @@ fields:
action: hide
field: news_enabled
condition: unchecked
indexnow_enabled:
label: golem15.sitemap::lang.settings.indexnow_enabled
comment: golem15.sitemap::lang.settings.indexnow_enabled_comment
type: switch
default: false
span: full
tab: golem15.sitemap::lang.settings.tab_indexnow
_indexnow_status:
type: partial
path: indexnow_status
span: full
tab: golem15.sitemap::lang.settings.tab_indexnow
# No column: the controller lists it in FormVirtualFields and generates
# a new key on save when it is checked.
indexnow_new_key:
label: golem15.sitemap::lang.settings.indexnow_new_key
comment: golem15.sitemap::lang.settings.indexnow_new_key_comment
type: checkbox
default: false
span: full
tab: golem15.sitemap::lang.settings.tab_indexnow
trigger:
action: hide
field: indexnow_enabled
condition: unchecked