cc-os/plugins/os-backlog/lib/backlog/board_resolver.rb

87 lines
2.9 KiB
Ruby
Raw Normal View History

require_relative "config"
module Backlog
# Resolves a board name to its Planka board record, caching the resolved
# id as `board_id=<id>` in the repo's `.cc-os/config` (issue #22).
#
# The tracker's board *name* stays the source of truth; the id is only a
# cache. The cached id is honored only when the config's tracker is
# exactly `planka:<board_name>`, and a cached `boards.get(id)` hit counts
# only if the returned board's name still matches — a 404 or a rename
# falls back to the projects+boards name lookup and rewrites the cache.
# Any cache failure (unreadable config, unwritable config, stale id)
# degrades to the name lookup; it never fails the command.
#
# Filesystem access is injected: `read_config` returns the raw
# `.cc-os/config` contents (or nil), `write_config` persists updated
# contents. Cache writes go through Config.set, the comment-preserving
# writer — never Config.merge.
class BoardResolver
# @param client [Planka::Client] an already-authenticated client
# @param read_config [#call] -> String, nil — raw .cc-os/config contents
# @param write_config [#call] (String) -> void — persist updated contents
def initialize(client:, read_config:, write_config:)
@client = client
@read_config = read_config
@write_config = write_config
end
# A resolver with no config access: always name-lookup, never caches.
def self.null(client:)
new(client: client, read_config: -> {}, write_config: ->(_) {})
end
# @param name [String] the board name (source of truth)
# @return [Planka::Types::Board]
# @raise [RuntimeError] if no board with that name exists
def board_for(name)
cached = cached_board(name)
return cached if cached
board = lookup_by_name!(name)
cache_id(name, board.id)
board
end
private
def cached_board(name)
config = safe_config
return nil unless config && config.tracker == "planka:#{name}"
id = config.board_id
return nil unless id
board = @client.boards.get(id).board
board && board.name == name ? board : nil
rescue StandardError
nil
end
def lookup_by_name!(name)
@client.projects.list.each do |project|
board = @client.boards.list(project.id).find { |b| b.name == name }
return board if board
end
raise "no board named #{name.inspect} found (run board-ensure first)"
end
def cache_id(name, id)
contents = @read_config.call
config = Config.new(contents)
return unless config.tracker == "planka:#{name}"
return if config.board_id == id.to_s
@write_config.call(Config.set(contents, "board_id", id))
rescue StandardError
nil # cache write failure never fails the command
end
def safe_config
Config.new(@read_config.call)
rescue StandardError
nil
end
end
end