1# frozen_string_literal: true
2
3class Badge < ApplicationRecord
4  include FromUnion
5
6  # This structure sets the placeholders that the urls
7  # can have. This hash also sets which action to ask when
8  # the placeholder is found.
9  PLACEHOLDERS = {
10    'project_path' => :full_path,
11    'project_id' => :id,
12    'default_branch' => :default_branch,
13    'commit_sha' => ->(project) { project.commit&.sha }
14  }.freeze
15
16  # This regex is built dynamically using the keys from the PLACEHOLDER struct.
17  # So, we can easily add new placeholder just by modifying the PLACEHOLDER hash.
18  # This regex will build the new PLACEHOLDER_REGEX with the new information
19  PLACEHOLDERS_REGEX = /(#{PLACEHOLDERS.keys.join('|')})/.freeze
20
21  default_scope { order_created_at_asc } # rubocop:disable Cop/DefaultScope
22
23  scope :order_created_at_asc, -> { reorder(created_at: :asc) }
24
25  scope :with_name, ->(name) { where(name: name) }
26
27  validates :link_url, :image_url, addressable_url: true
28  validates :type, presence: true
29
30  def rendered_link_url(project = nil)
31    build_rendered_url(link_url, project)
32  end
33
34  def rendered_image_url(project = nil)
35    Gitlab::AssetProxy.proxy_url(
36      build_rendered_url(image_url, project)
37    )
38  end
39
40  private
41
42  def build_rendered_url(url, project = nil)
43    return url unless valid? && project
44
45    Gitlab::StringPlaceholderReplacer.replace_string_placeholders(url, PLACEHOLDERS_REGEX) do |arg|
46      replace_placeholder_action(PLACEHOLDERS[arg], project)
47    end
48  end
49
50  # The action param represents the :symbol or Proc to call in order
51  # to retrieve the return value from the project.
52  # This method checks if it is a Proc and use the call method, and if it is
53  # a symbol just send the action
54  def replace_placeholder_action(action, project)
55    return unless project
56
57    action.is_a?(Proc) ? action.call(project) : project.public_send(action) # rubocop:disable GitlabSecurity/PublicSend
58  end
59end
60