bingo: add, delete and reorder datacenters from admin
The card was fixed to the 25 seeded rows, so a new site such as IXcellerate MOS7 (34 MW, under construction) could only be shoehorned in by overwriting an existing row. Add create and delete (cascading to incidents), make position an insert that shifts the rest, and keep positions dense. More than 25 rows are allowed: the card shows the first 25 and the rest wait in reserve. "Sort by MW" re-ranks by the parsed power figure, unknown last. Assisted-by: Claude:opus-5
This commit is contained in:
parent
cd04b01e57
commit
e725395825
6 changed files with 197 additions and 14 deletions
28
lib/bingo.rb
28
lib/bingo.rb
|
|
@ -238,6 +238,34 @@ module Bingo
|
|||
end
|
||||
end
|
||||
|
||||
# Positions stay dense 1..N; the card shows the first SIZE*SIZE, the rest
|
||||
# wait in reserve. Placing a datacenter inserts it at pos (default: last)
|
||||
# and shifts everyone after it.
|
||||
def self.place!(dc, pos = nil)
|
||||
DB.connection.transaction do
|
||||
ids = Datacenter.exclude(id: dc.id).order(:position, :id).select_map(:id)
|
||||
pos = (pos || ids.size + 1).clamp(1, ids.size + 1)
|
||||
renumber!(ids.insert(pos - 1, dc.id))
|
||||
end
|
||||
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) }
|
||||
end
|
||||
|
||||
# "64" → 64.0, "25–30" → 30.0, ">40" → 40.0, "13,7" → 13.7, "" → nil
|
||||
def self.mw_value(mw)
|
||||
mw.to_s.tr(',', '.').scan(/\d+(?:\.\d+)?/).map(&:to_f).max
|
||||
end
|
||||
|
||||
# Rank by power, unknown last; ties keep their current order.
|
||||
def self.sort_by_mw!
|
||||
DB.connection.transaction do
|
||||
renumber!(Datacenter.order(:position, :id).all
|
||||
.sort_by.with_index { |dc, i| [-(mw_value(dc.mw) || -1), i] }.map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
# First boot: fill the card (and any already-known incidents) from
|
||||
# db/bingo_seed.json. Never touches rows once any exist; edits happen in
|
||||
# /admin/bingo.
|
||||
|
|
|
|||
|
|
@ -15,6 +15,21 @@ module BingoAdmin
|
|||
s.empty? || s.match?(URI::DEFAULT_PARSER.make_regexp(%w[http https]))
|
||||
end
|
||||
|
||||
def position_param
|
||||
params[:position].to_s =~ /\A\d+\z/ ? params[:position].to_i : nil
|
||||
end
|
||||
|
||||
# Copies the edit form onto dc (position is handled by Bingo.place!).
|
||||
def assign_dc(dc)
|
||||
DC_TEXT.each { |f| dc[f.to_sym] = blank_nil(params[f]) }
|
||||
dc.year = params[:year].to_s =~ /\A\d{4}\z/ ? params[:year].to_i : nil
|
||||
dc.status = 'operational' unless %w[operational building].include?(dc.status)
|
||||
errs = []
|
||||
errs << 'Name (ru), city (ru) and operator are required' if [dc.name_ru, dc.city_ru, dc.operator].any?(&:nil?)
|
||||
errs << 'URLs must be http(s)' unless [dc.photo_source_url, dc.info_url].all? { |u| http_url?(u.to_s) }
|
||||
errs
|
||||
end
|
||||
|
||||
def blank_nil(s)
|
||||
s = s.to_s.strip
|
||||
s.empty? ? nil : s
|
||||
|
|
@ -33,6 +48,33 @@ module BingoAdmin
|
|||
erb :'admin/bingo/index'
|
||||
end
|
||||
|
||||
app.get '/admin/bingo/dc/new' do
|
||||
@dc = Bingo::Datacenter.new(status: 'operational')
|
||||
@page_title = 'New datacenter · bingo admin'
|
||||
@noindex = true
|
||||
erb :'admin/bingo/edit'
|
||||
end
|
||||
|
||||
app.post '/admin/bingo/dc' do
|
||||
@dc = Bingo::Datacenter.new
|
||||
@dc.slug = params[:slug].to_s.strip.downcase
|
||||
errs = assign_dc(@dc)
|
||||
errs.unshift('Slug: lowercase letters, digits and dashes') unless @dc.slug.match?(/\A[a-z0-9]+(-[a-z0-9]+)*\z/)
|
||||
errs.unshift('Slug already taken') if Bingo::Datacenter.where(slug: @dc.slug).any?
|
||||
if errs.any?
|
||||
@flash = errs.join('; ')
|
||||
@page_title = 'New datacenter · bingo admin'
|
||||
@noindex = true
|
||||
status 422
|
||||
return erb :'admin/bingo/edit'
|
||||
end
|
||||
@dc.position = Bingo::Datacenter.count + 1
|
||||
@dc.save
|
||||
Bingo.place!(@dc, position_param)
|
||||
session[:bingo_flash] = "Added #{@dc.name_ru} at ##{@dc.refresh.position}."
|
||||
redirect "/admin/bingo/dc/#{@dc.id}"
|
||||
end
|
||||
|
||||
app.get '/admin/bingo/dc/:id' do
|
||||
@dc = Bingo::Datacenter[params[:id].to_i] or halt 404
|
||||
@page_title = "#{@dc.name_ru} · bingo admin"
|
||||
|
|
@ -43,15 +85,10 @@ module BingoAdmin
|
|||
|
||||
app.post '/admin/bingo/dc/:id' do
|
||||
dc = Bingo::Datacenter[params[:id].to_i] or halt 404
|
||||
DC_TEXT.each { |f| dc[f.to_sym] = blank_nil(params[f]) }
|
||||
dc.position = params[:position].to_i if params[:position].to_s =~ /\A\d+\z/
|
||||
dc.year = params[:year].to_s =~ /\A\d{4}\z/ ? params[:year].to_i : nil
|
||||
dc.status = 'operational' unless %w[operational building].include?(dc.status)
|
||||
errs = []
|
||||
errs << 'Name (ru), city (ru) and operator are required' if [dc.name_ru, dc.city_ru, dc.operator].any?(&:nil?)
|
||||
errs << 'URLs must be http(s)' unless [dc.photo_source_url, dc.info_url].all? { |u| http_url?(u.to_s) }
|
||||
errs = assign_dc(dc)
|
||||
if errs.empty?
|
||||
dc.save_changes
|
||||
Bingo.place!(dc, position_param) if position_param && position_param != dc.position
|
||||
session[:bingo_flash] = 'Saved.'
|
||||
else
|
||||
session[:bingo_flash] = errs.join('; ')
|
||||
|
|
@ -59,6 +96,25 @@ module BingoAdmin
|
|||
redirect "/admin/bingo/dc/#{dc.id}"
|
||||
end
|
||||
|
||||
# Deletes the row and (FK cascade) its incidents; the photo object stays
|
||||
# in the bucket.
|
||||
app.post '/admin/bingo/dc/:id/delete' do
|
||||
dc = Bingo::Datacenter[params[:id].to_i] or halt 404
|
||||
n = dc.incidents.size
|
||||
DB.connection.transaction do
|
||||
dc.destroy
|
||||
Bingo.renumber!
|
||||
end
|
||||
session[:bingo_flash] = "Deleted #{dc.name_ru}#{" and #{n} incident(s)" if n.positive?}."
|
||||
redirect '/admin/bingo'
|
||||
end
|
||||
|
||||
app.post '/admin/bingo/sort' do
|
||||
Bingo.sort_by_mw!
|
||||
session[:bingo_flash] = 'Sorted by MW (unknown last).'
|
||||
redirect '/admin/bingo'
|
||||
end
|
||||
|
||||
# Either an uploaded file or "import from URL" (defaults to photo_source_url).
|
||||
app.post '/admin/bingo/dc/:id/photo' do
|
||||
dc = Bingo::Datacenter[params[:id].to_i] or halt 404
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue