bingo: lastmod from the last edit, not the last insert
The "updated" date and the sitemap lastmod came from max(created_at), so editing a datacenter, fixing an incident title or deleting an incident left them stale. Google stops trusting lastmod once it is seen to be wrong. Migration 014 adds updated_at (backfilled from created_at). Saves set it, renumbering bumps only the rows whose position actually moved, and deleting an incident bumps its datacenter. The admin delete now goes through the model so the hook runs. The sitemap gives lastmod as a full timestamp and drops changefreq, which Google ignores. Assisted-by: Claude:opus-5
This commit is contained in:
parent
b100b6a8cd
commit
6f37e537ca
5 changed files with 54 additions and 7 deletions
13
db/migrations/014_bingo_updated_at.rb
Normal file
13
db/migrations/014_bingo_updated_at.rb
Normal file
|
|
@ -0,0 +1,13 @@
|
|||
Sequel.migration do
|
||||
up do
|
||||
# Last change to the row, for the "updated" date and sitemap lastmod.
|
||||
%i[bingo_datacenters bingo_incidents].each do |t|
|
||||
add_column t, :updated_at, DateTime
|
||||
from(t).update(updated_at: :created_at)
|
||||
end
|
||||
end
|
||||
|
||||
down do
|
||||
%i[bingo_datacenters bingo_incidents].each { |t| drop_column t, :updated_at }
|
||||
end
|
||||
end
|
||||
27
lib/bingo.rb
27
lib/bingo.rb
|
|
@ -99,6 +99,11 @@ module Bingo
|
|||
super
|
||||
end
|
||||
|
||||
def before_save
|
||||
self.updated_at = Time.now.utc
|
||||
super
|
||||
end
|
||||
|
||||
def operational? = status == 'operational'
|
||||
def planned? = status == 'planned'
|
||||
|
||||
|
|
@ -126,10 +131,21 @@ module Bingo
|
|||
include Localized
|
||||
many_to_one :datacenter, class: 'Bingo::Datacenter'
|
||||
|
||||
# A removed incident changes its datacenter's cell.
|
||||
def after_destroy
|
||||
super
|
||||
Datacenter.where(id: datacenter_id).update(updated_at: Time.now.utc)
|
||||
end
|
||||
|
||||
def before_create
|
||||
self.created_at ||= Time.now.utc
|
||||
super
|
||||
end
|
||||
|
||||
def before_save
|
||||
self.updated_at = Time.now.utc
|
||||
super
|
||||
end
|
||||
end
|
||||
|
||||
# Board state for rendering: ordered cells, hits and completed lines.
|
||||
|
|
@ -145,7 +161,7 @@ module Bingo
|
|||
hit = cells.map { |dc| dc.incidents.any? }
|
||||
wins = LINES.select { |line| line.all? { |i| hit[i] } }
|
||||
incidents = cells.flat_map(&:incidents).sort_by { |i| [i.occurred_on, i.id] }.reverse
|
||||
updated = [Datacenter.max(:created_at), Incident.max(:created_at)].compact.max
|
||||
updated = [Datacenter.max(:updated_at), Incident.max(:updated_at)].compact.max
|
||||
new(cells, hit, wins, incidents, updated, view)
|
||||
end
|
||||
|
||||
|
|
@ -364,7 +380,10 @@ module Bingo
|
|||
end
|
||||
|
||||
def self.renumber!(ids = Datacenter.order(:position, :id).select_map(:id))
|
||||
ids.each_with_index { |id, i| Datacenter.where(id: id).update(position: i + 1) }
|
||||
now = Time.now.utc
|
||||
ids.each_with_index do |id, i|
|
||||
Datacenter.where(id: id).exclude(position: i + 1).update(position: i + 1, updated_at: now)
|
||||
end
|
||||
end
|
||||
|
||||
# "64" → 64.0, "25–30" → 30.0, ">40" → 40.0, "13,7" → 13.7, "" → nil
|
||||
|
|
@ -388,14 +407,14 @@ module Bingo
|
|||
path = File.join($root, 'db', 'bingo_seed.json')
|
||||
return unless File.exist?(path)
|
||||
seed = JSON.parse(File.read(path, encoding: 'UTF-8'))
|
||||
cols = Datacenter.columns.map(&:to_s) - %w[id created_at]
|
||||
cols = Datacenter.columns.map(&:to_s) - %w[id created_at updated_at]
|
||||
DB.connection.transaction do
|
||||
ids = seed.fetch('datacenters').each_with_index.to_h do |row, i|
|
||||
dc = Datacenter.create(row.slice(*cols).merge('position' => row['position'] || i + 1))
|
||||
[dc.slug, dc.id]
|
||||
end
|
||||
seed.fetch('incidents', []).each do |row|
|
||||
Incident.create(row.slice(*(Incident.columns.map(&:to_s) - %w[id created_at datacenter_id]))
|
||||
Incident.create(row.slice(*(Incident.columns.map(&:to_s) - %w[id created_at updated_at datacenter_id]))
|
||||
.merge('datacenter_id' => ids.fetch(row.fetch('slug'))))
|
||||
end
|
||||
end
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ module BingoAdmin
|
|||
|
||||
app.post '/admin/bingo/incidents/:id/delete' do
|
||||
inc = Bingo::Incident[params[:id].to_i]
|
||||
Bingo::Incident.where(id: params[:id].to_i).delete
|
||||
inc&.destroy # hook bumps the datacenter's updated_at
|
||||
audit!('bingo.incident_delete', inc&.datacenter&.slug, inc && "#{inc.occurred_on} #{inc.title_ru}")
|
||||
session[:bingo_flash] = 'Incident deleted.'
|
||||
redirect '/admin/bingo'
|
||||
|
|
|
|||
|
|
@ -175,6 +175,21 @@ class BingoTest < Minitest::Test
|
|||
assert last_response.ok?
|
||||
%w[ru en uk].each { |l| assert_includes last_response.body, "<loc>https://example.org/#{l}</loc>" }
|
||||
assert_includes last_response.body, 'hreflang="x-default"'
|
||||
refute_includes last_response.body, 'changefreq'
|
||||
assert_match %r{</loc>\n <lastmod>\d{4}-\d\d-\d\dT\d\d:\d\d:\d\dZ</lastmod>\n <xhtml:link}, last_response.body
|
||||
end
|
||||
|
||||
def test_updated_on_follows_edits_and_deletes
|
||||
old = Time.utc(2026, 1, 1)
|
||||
DB.connection[:bingo_datacenters].update(created_at: old, updated_at: old)
|
||||
assert_equal old, Bingo::Board.load.updated_on
|
||||
@dcs[5].refresh.update(name_en: 'Renamed')
|
||||
assert_operator Bingo::Board.load.updated_on, :>, old
|
||||
DB.connection[:bingo_datacenters].update(updated_at: old)
|
||||
inc = hit!(2)
|
||||
DB.connection[:bingo_incidents].update(created_at: old, updated_at: old)
|
||||
inc.refresh.destroy
|
||||
assert_operator Bingo::Board.load.updated_on, :>, old
|
||||
end
|
||||
|
||||
def test_asxpio_static_files_not_served
|
||||
|
|
|
|||
|
|
@ -3,8 +3,8 @@
|
|||
<% Bingo::LOCALES.each do |l| %>
|
||||
<url>
|
||||
<loc><%= base_url %>/<%= l %></loc>
|
||||
<% if @lastmod %> <lastmod><%= @lastmod.utc.strftime('%Y-%m-%d') %></lastmod>
|
||||
<% end %> <changefreq>daily</changefreq>
|
||||
<% if @lastmod %> <lastmod><%= @lastmod.utc.iso8601 %></lastmod>
|
||||
<% end %>
|
||||
<% Bingo::LOCALES.each do |alt| %> <xhtml:link rel="alternate" hreflang="<%= alt %>" href="<%= base_url %>/<%= alt %>"/>
|
||||
<% end %> <xhtml:link rel="alternate" hreflang="x-default" href="<%= base_url %>/"/>
|
||||
</url>
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue