bingo: optional prose per datacenter page

The datacenter pages carry only generated sentences, which is thin
content to rank on. Migration 015 adds about_ru/en/uk, edited in the
admin form and rendered as paragraphs below the facts.

There is deliberately no fallback to ru: Russian prose on the English
page would hurt more than an absent paragraph.

Assisted-by: Claude:opus-5
This commit is contained in:
Sergei Poljanski 2026-10-10 09:41:11 +04:00
commit ca48f4292b
Signed by: asxpi
GPG key ID: 4F8851660FA4121B
6 changed files with 27 additions and 1 deletions

View file

@ -0,0 +1,7 @@
Sequel.migration do
change do
# Optional prose for the datacenter page, per locale. No fallback to ru:
# Russian text on the en page would be worse than none.
%w[ru en uk].each { |l| add_column :bingo_datacenters, :"about_#{l}", String, text: true }
end
end

View file

@ -143,6 +143,11 @@ module Bingo
key.delete_prefix(Photo::PREFIX)
end
# Paragraphs of the datacenter page's prose in exactly this locale.
def about(locale)
self[:"about_#{locale}"].to_s.strip.split(/\n\s*\n/).map(&:strip)
end
# 1200x630 JPEG for og:image (every preview client renders it); the
# webp dialog variant until it exists.
def og_file

View file

@ -4,7 +4,7 @@ require 'uri'
# Registered into AsxpioWeb, so AdminAuth (HTTP Basic) and CSRF apply.
module BingoAdmin
DC_TEXT = %w[name_ru name_en name_uk city_ru city_en city_uk operator mw mw_type status
photo_credit photo_source_url info_url].freeze
photo_credit photo_source_url info_url about_ru about_en about_uk].freeze
module Helpers
def bingo_public_url

View file

@ -252,6 +252,16 @@ class BingoTest < Minitest::Test
refute_includes last_response.body, 'rel="prev"'
end
def test_dc_about_text
@app = AsxpioWeb
token = admin_token("/admin/bingo/dc/#{@dcs[1].id}")
post "/admin/bingo/dc/#{@dcs[1].id}", dc_params(authenticity_token: token, about_en: "First <b>para</b>.\r\n\r\nSecond."), admin_env
assert_equal "First <b>para</b>.\r\n\r\nSecond.", @dcs[1].refresh.about_en
public = Rack::MockRequest.new(BingoWeb) # rack-test's session is bound to AsxpioWeb now
assert_includes public.get('/en/dc/dc-2').body, '<p class="facts">First &lt;b&gt;para&lt;/b&gt;.</p><p class="facts">Second.</p>'
refute_includes public.get('/ru/dc/dc-2').body, 'Second.' # no fallback across languages
end
def test_reserve_dc_has_a_page
Bingo::Datacenter.create(slug: 'dc-26', position: 26, name_ru: 'ЦОД 26', city_ru: 'Тверь', operator: 'Op')
get '/ru/dc/dc-26'

View file

@ -65,6 +65,9 @@
<div class="field"><label for="photo_credit">Photo credit</label><input type="text" id="photo_credit" name="photo_credit" value="<%= @dc.photo_credit %>" /></div>
<div class="field"><label for="photo_source_url">Photo source URL</label><input type="url" id="photo_source_url" name="photo_source_url" value="<%= @dc.photo_source_url %>" /></div>
<div class="field"><label for="info_url">Info / fact source URL</label><input type="url" id="info_url" name="info_url" value="<%= @dc.info_url %>" /></div>
<% %w[ru en uk].each do |l| %>
<div class="field"><label for="about_<%= l %>">About (<%= l %>) <span class="optional">(datacenter page; blank lines split paragraphs; not translated automatically, blank = hidden)</span></label><textarea id="about_<%= l %>" name="about_<%= l %>" rows="4" maxlength="5000"><%= @dc[:"about_#{l}"] %></textarea></div>
<% end %>
<% if can?('bingo', 'write') %><button type="submit" class="download-button"><%= fresh ? 'Add datacenter' : 'Save' %></button><% end %>
</fieldset>
</form>

View file

@ -14,6 +14,7 @@
<h1><%= dc.l(:name, @locale) %></h1>
<%== erb :_dc_meta, layout: false, locals: { dc: dc } %>
<p class="facts"><%= dc_facts(dc).join(' ') %></p>
<% dc.about(@locale).each do |para| %><p class="facts"><%= para %></p><% end %>
<section>
<h2><%= t(:incidents) %></h2>
<%== erb :_dc_incidents, layout: false, locals: { dc: dc } %>