Implement a setting to retrieve high res logos (#725)

* Implement a setting to retrieve high res logos

* Update _brand_fetch_settings.html.erb

* Add fallback for stock tickers also to use Brandfetch

* Update security.rb

* Update toggle logic for high-res logos setting

Signed-off-by: Juan José Mata <jjmata@jjmata.com>

* Update security.rb

* Update security.rb

---------

Signed-off-by: Juan José Mata <jjmata@jjmata.com>
Co-authored-by: Juan José Mata <jjmata@jjmata.com>
This commit is contained in:
soky srm
2026-01-21 17:16:51 +01:00
committed by GitHub
parent ae61df4978
commit abab66675c
14 changed files with 84 additions and 17 deletions

View File

@@ -46,6 +46,10 @@ class Settings::HostingsController < ApplicationController
Setting.brand_fetch_client_id = hosting_params[:brand_fetch_client_id]
end
if hosting_params.key?(:brand_fetch_high_res_logos)
Setting.brand_fetch_high_res_logos = hosting_params[:brand_fetch_high_res_logos] == "1"
end
if hosting_params.key?(:twelve_data_api_key)
Setting.twelve_data_api_key = hosting_params[:twelve_data_api_key]
end
@@ -126,7 +130,7 @@ class Settings::HostingsController < ApplicationController
private
def hosting_params
params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :brand_fetch_client_id, :twelve_data_api_key, :openai_access_token, :openai_uri_base, :openai_model, :openai_json_mode, :exchange_rate_provider, :securities_provider, :syncs_include_pending, :auto_sync_enabled, :auto_sync_time)
params.require(:setting).permit(:onboarding_state, :require_email_confirmation, :brand_fetch_client_id, :brand_fetch_high_res_logos, :twelve_data_api_key, :openai_access_token, :openai_uri_base, :openai_model, :openai_json_mode, :exchange_rate_provider, :securities_provider, :syncs_include_pending, :auto_sync_enabled, :auto_sync_time)
end
def ensure_admin

View File

@@ -126,7 +126,8 @@ class Family::AutoMerchantDetector
def build_logo_url(business_url)
return nil unless Setting.brand_fetch_client_id.present? && business_url.present?
"#{default_logo_provider_url}/#{business_url}/icon/fallback/lettermark/w/40/h/40?c=#{Setting.brand_fetch_client_id}"
size = Setting.brand_fetch_logo_size
"#{default_logo_provider_url}/#{business_url}/icon/fallback/lettermark/w/#{size}/h/#{size}?c=#{Setting.brand_fetch_client_id}"
end
def enhance_provider_merchant(merchant, auto_detection)
@@ -138,7 +139,8 @@ class Family::AutoMerchantDetector
# Add logo if BrandFetch is configured
if Setting.brand_fetch_client_id.present?
updates[:logo_url] = "#{default_logo_provider_url}/#{auto_detection.business_url}/icon/fallback/lettermark/w/40/h/40?c=#{Setting.brand_fetch_client_id}"
size = Setting.brand_fetch_logo_size
updates[:logo_url] = "#{default_logo_provider_url}/#{auto_detection.business_url}/icon/fallback/lettermark/w/#{size}/h/#{size}?c=#{Setting.brand_fetch_client_id}"
end
end

View File

@@ -21,7 +21,8 @@ class FamilyMerchant < Merchant
def generate_logo_url_from_website
if website_url.present? && Setting.brand_fetch_client_id.present?
domain = extract_domain(website_url)
self.logo_url = "https://cdn.brandfetch.io/#{domain}/icon/fallback/lettermark/w/40/h/40?c=#{Setting.brand_fetch_client_id}"
size = Setting.brand_fetch_logo_size
self.logo_url = "https://cdn.brandfetch.io/#{domain}/icon/fallback/lettermark/w/#{size}/h/#{size}?c=#{Setting.brand_fetch_client_id}"
elsif website_url.blank?
self.logo_url = nil
end

View File

@@ -7,6 +7,7 @@ class Security < ApplicationRecord
EXCHANGES = YAML.safe_load_file(Rails.root.join("config", "exchanges.yml")).freeze
before_validation :upcase_symbols
before_save :generate_logo_url_from_brandfetch, if: :should_generate_logo?
has_many :trades, dependent: :nullify, class_name: "Trade"
has_many :prices, dependent: :destroy
@@ -42,13 +43,18 @@ class Security < ApplicationRecord
)
end
def brandfetch_icon_url(width: 40, height: 40)
return nil unless Setting.brand_fetch_client_id.present? && website_url.present?
def brandfetch_icon_url(width: nil, height: nil)
return nil unless Setting.brand_fetch_client_id.present?
domain = extract_domain(website_url)
return nil unless domain.present?
w = width || Setting.brand_fetch_logo_size
h = height || Setting.brand_fetch_logo_size
"https://cdn.brandfetch.io/#{domain}/icon/fallback/lettermark/w/#{width}/h/#{height}?c=#{Setting.brand_fetch_client_id}"
identifier = extract_domain(website_url) if website_url.present?
identifier ||= ticker
return nil unless identifier.present?
"https://cdn.brandfetch.io/#{identifier}/icon/fallback/lettermark/w/#{w}/h/#{h}?c=#{Setting.brand_fetch_client_id}"
end
private
@@ -65,4 +71,18 @@ class Security < ApplicationRecord
self.ticker = ticker.upcase
self.exchange_operating_mic = exchange_operating_mic.upcase if exchange_operating_mic.present?
end
def should_generate_logo?
url = brandfetch_icon_url
return false unless url.present?
return true if logo_url.blank?
return false unless logo_url.include?("cdn.brandfetch.io")
website_url_changed? || ticker_changed?
end
def generate_logo_url_from_brandfetch
self.logo_url = brandfetch_icon_url
end
end

View File

@@ -11,6 +11,23 @@ class Setting < RailsSettings::Base
field :openai_model, type: :string, default: ENV["OPENAI_MODEL"]
field :openai_json_mode, type: :string, default: ENV["LLM_JSON_MODE"]
field :brand_fetch_client_id, type: :string, default: ENV["BRAND_FETCH_CLIENT_ID"]
field :brand_fetch_high_res_logos, type: :boolean, default: ENV.fetch("BRAND_FETCH_HIGH_RES_LOGOS", "false") == "true"
BRAND_FETCH_LOGO_SIZE_STANDARD = 40
BRAND_FETCH_LOGO_SIZE_HIGH_RES = 120
BRAND_FETCH_URL_PATTERN = %r{(https://cdn\.brandfetch\.io/[^/]+/icon/fallback/lettermark/)w/\d+/h/\d+(\?c=.+)}
def self.brand_fetch_logo_size
brand_fetch_high_res_logos ? BRAND_FETCH_LOGO_SIZE_HIGH_RES : BRAND_FETCH_LOGO_SIZE_STANDARD
end
# Transforms a stored Brandfetch URL to use the current logo size setting
def self.transform_brand_fetch_url(url)
return url unless url.present? && url.match?(BRAND_FETCH_URL_PATTERN)
size = brand_fetch_logo_size
url.gsub(BRAND_FETCH_URL_PATTERN, "\\1w/#{size}/h/#{size}\\2")
end
# Provider selection
field :exchange_rate_provider, type: :string, default: ENV.fetch("EXCHANGE_RATE_PROVIDER", "twelve_data")

View File

@@ -8,7 +8,8 @@
} %>
<% if account.institution_domain.present? && Setting.brand_fetch_client_id.present? %>
<%= image_tag "https://cdn.brandfetch.io/#{account.institution_domain}/icon/fallback/lettermark/w/40/h/40?c=#{Setting.brand_fetch_client_id}", class: "shrink-0 rounded-full #{size_classes[size]}" %>
<% logo_size = Setting.brand_fetch_logo_size %>
<%= image_tag "https://cdn.brandfetch.io/#{account.institution_domain}/icon/fallback/lettermark/w/#{logo_size}/h/#{logo_size}?c=#{Setting.brand_fetch_client_id}", class: "shrink-0 rounded-full #{size_classes[size]}" %>
<% elsif account.logo_url.present? %>
<%= image_tag account.logo_url, class: "shrink-0 rounded-full #{size_classes[size]}", loading: "lazy" %>
<% elsif account.logo.attached? %>

View File

@@ -5,7 +5,7 @@
<div class="flex w-full items-center gap-2.5">
<% if family_merchant.logo_url %>
<div class="w-8 h-8 rounded-full flex justify-center items-center">
<%= image_tag family_merchant.logo_url, class: "w-8 h-8 rounded-full" %>
<%= image_tag Setting.transform_brand_fetch_url(family_merchant.logo_url), class: "w-8 h-8 rounded-full" %>
</div>
<% else %>
<%= render partial: "shared/color_avatar", locals: { name: family_merchant.name, color: family_merchant.color } %>

View File

@@ -5,7 +5,7 @@
<div class="flex w-full items-center gap-2.5">
<% if provider_merchant.logo_url %>
<div class="w-8 h-8 rounded-full flex justify-center items-center">
<%= image_tag provider_merchant.logo_url, class: "w-8 h-8 rounded-full" %>
<%= image_tag Setting.transform_brand_fetch_url(provider_merchant.logo_url), class: "w-8 h-8 rounded-full" %>
</div>
<% else %>
<div class="w-8 h-8 rounded-full flex items-center justify-center bg-container-inset text-secondary text-sm font-medium">

View File

@@ -6,7 +6,7 @@
<%= content_tag :div, class: ["flex items-center gap-2"] do %>
<% if recurring_transaction.merchant.present? %>
<% if recurring_transaction.merchant.logo_url.present? %>
<%= image_tag recurring_transaction.merchant.logo_url,
<%= image_tag Setting.transform_brand_fetch_url(recurring_transaction.merchant.logo_url),
class: "w-6 h-6 rounded-full",
loading: "lazy" %>
<% else %>

View File

@@ -76,8 +76,10 @@
<tr>
<td class="px-4 py-3">
<div class="flex items-center gap-3">
<% if holding.security.logo_url.present? %>
<img src="<%= holding.security.logo_url %>" alt="<%= holding.ticker %>" class="w-6 h-6 rounded-full">
<% if holding.security.brandfetch_icon_url.present? %>
<img src="<%= holding.security.brandfetch_icon_url %>" alt="<%= holding.ticker %>" class="w-6 h-6 rounded-full">
<% elsif holding.security.logo_url.present? %>
<img src="<%= Setting.transform_brand_fetch_url(holding.security.logo_url) %>" alt="<%= holding.ticker %>" class="w-6 h-6 rounded-full">
<% else %>
<div class="w-6 h-6 rounded-full bg-container flex items-center justify-center text-xs font-medium text-secondary">
<%= holding.ticker[0..1] %>

View File

@@ -39,4 +39,21 @@
disabled: ENV["BRAND_FETCH_CLIENT_ID"].present?,
data: { "auto-submit-form-target": "auto" } %>
<% end %>
<div class="flex items-center justify-between mt-4">
<div class="space-y-1">
<p class="text-sm"><%= t(".high_res_label") %></p>
<p class="text-secondary text-sm"><%= t(".high_res_description") %></p>
</div>
<%= styled_form_with model: Setting.new,
url: settings_hosting_path,
method: :patch,
data: { controller: "auto-submit-form", auto_submit_form_trigger_event_value: "change" } do |form| %>
<%= form.toggle :brand_fetch_high_res_logos,
checked: ENV["BRAND_FETCH_HIGH_RES_LOGOS"].present? ? ENV["BRAND_FETCH_HIGH_RES_LOGOS"] == "true" : Setting.brand_fetch_high_res_logos,
disabled: ENV["BRAND_FETCH_HIGH_RES_LOGOS"].present?,
data: { auto_submit_form_target: "auto" } %>
<% end %>
</div>
</div>

View File

@@ -21,7 +21,7 @@
<%= content_tag :div, class: ["flex items-center gap-3 lg:gap-4"] do %>
<div class="hidden lg:flex">
<% if transaction.merchant&.logo_url.present? %>
<%= image_tag transaction.merchant.logo_url,
<%= image_tag Setting.transform_brand_fetch_url(transaction.merchant.logo_url),
class: "w-9 h-9 rounded-full",
loading: "lazy" %>
<% else %>
@@ -38,7 +38,7 @@
<div class="flex md:hidden items-center gap-1 col-span-2 relative">
<%= render "transactions/transaction_category", transaction: transaction, variant: "mobile" %>
<% if transaction.merchant&.logo_url.present? %>
<%= image_tag transaction.merchant.logo_url,
<%= image_tag Setting.transform_brand_fetch_url(transaction.merchant.logo_url),
class: "w-5 h-5 rounded-full absolute -bottom-1 -right-1 border border-secondary pointer-events-none",
loading: "lazy" %>
<% end %>