new framework

This commit is contained in:
2026-05-12 22:36:19 +02:00
parent d92768dd23
commit 9d52a2e2c6
29 changed files with 1240 additions and 400 deletions

View File

@@ -0,0 +1,83 @@
# frozen_string_literal: true
require 'csv'
require 'time'
require 'fileutils'
require_relative '../model/message_model'
# Multi-board store. Each board ("general", "tech", "offtopic"…) maps to one
# CSV file inside +base_dir+. Lazy-loaded the first time a board is touched.
class BoardRepository
BoardMeta = Struct.new(:id, :title, :description)
DEFAULT_BOARDS = [
BoardMeta.new('general', 'General', 'Open chat, anything goes'),
BoardMeta.new('tech', 'Tech', 'Hardware, software, code'),
BoardMeta.new('offtopic', 'Off-Topic','Random topics, jokes'),
BoardMeta.new('sysop', 'Sysop', 'Notes from the operator'),
].freeze
attr_reader :boards
def initialize(base_dir, boards: DEFAULT_BOARDS, legacy_path: nil)
@base_dir = base_dir
@boards = boards
@cache = {}
@mu = Mutex.new
FileUtils.mkdir_p(@base_dir)
migrate_legacy!(legacy_path) if legacy_path
end
def append(board_id, username, text)
msg = MessageModel.new(Time.now.strftime('%m-%d %H:%M'), username, text)
@mu.synchronize do
load_board(board_id) << msg
CSV.open(path_for(board_id), 'a') { |csv| csv << [msg.timestamp, msg.username, msg.text] }
end
msg
end
def last(board_id, n = 30)
@mu.synchronize { load_board(board_id).last(n) }
end
def count(board_id)
@mu.synchronize { load_board(board_id).size }
end
def total_count
@mu.synchronize { @boards.sum { |b| load_board(b.id).size } }
end
private
def migrate_legacy!(legacy_path)
return unless File.exist?(legacy_path)
target = path_for('general')
return if File.exist?(target) # already migrated or general has its own data
rows = []
CSV.foreach(legacy_path) { |row| rows << row }
return if rows.empty?
CSV.open(target, 'w') { |csv| rows.each { |r| csv << r } }
warn "BoardRepository: migrated #{rows.size} legacy messages " \
"from #{legacy_path} to #{target}"
rescue StandardError => e
warn "BoardRepository: legacy migration failed: #{e}"
end
def path_for(id)
File.join(@base_dir, "#{id}.csv")
end
def load_board(id)
return @cache[id] if @cache.key?(id)
list = []
if File.exist?(path_for(id))
CSV.foreach(path_for(id)) { |row| list << MessageModel.new(*row) }
end
@cache[id] = list
rescue StandardError => e
warn "BoardRepository load error (#{id}): #{e}"
@cache[id] = []
end
end