From 9fabcf4c7253468143ab2288b5060713208e8b8a Mon Sep 17 00:00:00 2001 From: Zach Gollwitzer Date: Mon, 9 Jun 2025 18:30:52 -0400 Subject: [PATCH] Redis check for self hosted apps (#2353) * Redis check for self hosted apps * Run linter with autocorrect * Add Redis to CI --- .github/workflows/ci.yml | 7 +++ app/controllers/concerns/self_hostable.rb | 27 +++++++++ app/controllers/pages_controller.rb | 11 ++-- .../accountable_sparklines/_error.html.erb | 2 +- app/views/accounts/_sparkline_error.html.erb | 2 +- .../accounts/new/_method_selector.html.erb | 4 +- app/views/chats/_chat.html.erb | 10 ++-- app/views/credit_cards/new.html.erb | 6 +- app/views/cryptos/new.html.erb | 6 +- app/views/depositories/new.html.erb | 6 +- app/views/imports/_table.html.erb | 2 +- app/views/investments/new.html.erb | 6 +- app/views/layouts/blank.html.erb | 13 ++++ app/views/loans/new.html.erb | 6 +- .../pages/dashboard/_balance_sheet.html.erb | 2 +- .../pages/redis_configuration_error.html.erb | 59 +++++++++++++++++++ .../plaid_items/_auto_link_opener.html.erb | 8 +-- app/views/plaid_items/edit.html.erb | 10 ++-- app/views/plaid_items/new.html.erb | 10 ++-- config/routes.rb | 2 + 20 files changed, 152 insertions(+), 47 deletions(-) create mode 100644 app/views/layouts/blank.html.erb create mode 100644 app/views/pages/redis_configuration_error.html.erb diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 2306ec69..eae6bb5e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -80,6 +80,7 @@ jobs: PLAID_CLIENT_ID: foo PLAID_SECRET: bar DATABASE_URL: postgres://postgres:postgres@localhost:5432 + REDIS_URL: redis://localhost:6379 RAILS_ENV: test services: @@ -92,6 +93,12 @@ jobs: - 5432:5432 options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3 + redis: + image: redis + ports: + - 6379:6379 + options: --health-cmd="redis-cli ping" --health-interval=10s --health-timeout=5s --health-retries=3 + steps: - name: Install packages run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libvips postgresql-client libpq-dev diff --git a/app/controllers/concerns/self_hostable.rb b/app/controllers/concerns/self_hostable.rb index a863f170..4208e03a 100644 --- a/app/controllers/concerns/self_hostable.rb +++ b/app/controllers/concerns/self_hostable.rb @@ -3,6 +3,8 @@ module SelfHostable included do helper_method :self_hosted?, :self_hosted_first_login? + + prepend_before_action :verify_self_host_config end private @@ -13,4 +15,29 @@ module SelfHostable def self_hosted_first_login? self_hosted? && User.count.zero? end + + def verify_self_host_config + return unless self_hosted? + + # Special handling for Redis configuration error page + if controller_name == "pages" && action_name == "redis_configuration_error" + # If Redis is now working, redirect to home + if redis_connected? + redirect_to root_path, notice: "Redis is now configured properly! You can now setup your Maybe application." + end + + return + end + + unless redis_connected? + redirect_to redis_configuration_error_path + end + end + + def redis_connected? + Redis.new.ping + true + rescue Redis::CannotConnectError + false + end end diff --git a/app/controllers/pages_controller.rb b/app/controllers/pages_controller.rb index b921f89c..a3694fb9 100644 --- a/app/controllers/pages_controller.rb +++ b/app/controllers/pages_controller.rb @@ -1,7 +1,8 @@ class PagesController < ApplicationController - skip_before_action :authenticate_user!, only: %i[early_access] include Periodable + skip_authentication only: :redis_configuration_error + def dashboard @balance_sheet = Current.family.balance_sheet @accounts = Current.family.accounts.active.with_attached_logo @@ -47,12 +48,8 @@ class PagesController < ApplicationController render layout: "settings" end - def early_access - redirect_to root_path if self_hosted? - - @invite_codes_count = InviteCode.count - @invite_code = InviteCode.order("RANDOM()").limit(1).first - render layout: false + def redis_configuration_error + render layout: "blank" end private diff --git a/app/views/accountable_sparklines/_error.html.erb b/app/views/accountable_sparklines/_error.html.erb index 70ce745e..f43f609f 100644 --- a/app/views/accountable_sparklines/_error.html.erb +++ b/app/views/accountable_sparklines/_error.html.erb @@ -5,4 +5,4 @@

Error

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/accounts/_sparkline_error.html.erb b/app/views/accounts/_sparkline_error.html.erb index 2f813a0d..dbee8dbf 100644 --- a/app/views/accounts/_sparkline_error.html.erb +++ b/app/views/accounts/_sparkline_error.html.erb @@ -5,4 +5,4 @@

Error

-<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/accounts/new/_method_selector.html.erb b/app/views/accounts/new/_method_selector.html.erb index e30c2463..03beaa92 100644 --- a/app/views/accounts/new/_method_selector.html.erb +++ b/app/views/accounts/new/_method_selector.html.erb @@ -11,7 +11,7 @@ <% if show_us_link %> <%# Default US-only Link %> - <%= link_to new_plaid_item_path(region: "us", accountable_type: accountable_type), + <%= link_to new_plaid_item_path(region: "us", accountable_type: accountable_type), class: "text-primary flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2", data: { turbo_frame: "modal" } do %> @@ -23,7 +23,7 @@ <%# EU Link %> <% if show_eu_link %> - <%= link_to new_plaid_item_path(region: "eu", accountable_type: accountable_type), + <%= link_to new_plaid_item_path(region: "eu", accountable_type: accountable_type), class: "text-primary flex items-center gap-4 w-full text-center focus:outline-hidden focus:bg-gray-50 border border-transparent focus:border focus:border-gray-200 px-2 hover:bg-gray-50 rounded-lg p-2", data: { turbo_frame: "modal" } do %> diff --git a/app/views/chats/_chat.html.erb b/app/views/chats/_chat.html.erb index c61c5a04..129cdbe7 100644 --- a/app/views/chats/_chat.html.erb +++ b/app/views/chats/_chat.html.erb @@ -13,12 +13,12 @@ <%= render MenuComponent.new(icon_vertical: true) do |menu| %> <% menu.with_item( - variant: "link", - text: "Edit chat title", - href: edit_chat_path(chat, ctx: "list"), - icon: "pencil", + variant: "link", + text: "Edit chat title", + href: edit_chat_path(chat, ctx: "list"), + icon: "pencil", frame: dom_id(chat, "title")) %> - + <% menu.with_item( variant: "button", text: "Delete chat", diff --git a/app/views/credit_cards/new.html.erb b/app/views/credit_cards/new.html.erb index f6cd7bca..95bca964 100644 --- a/app/views/credit_cards/new.html.erb +++ b/app/views/credit_cards/new.html.erb @@ -1,7 +1,7 @@ <% if params[:step] == "method_select" %> - <%= render "accounts/new/method_selector", - path: new_credit_card_path(return_to: params[:return_to]), - show_us_link: @show_us_link, + <%= render "accounts/new/method_selector", + path: new_credit_card_path(return_to: params[:return_to]), + show_us_link: @show_us_link, show_eu_link: @show_eu_link, accountable_type: "CreditCard" %> <% else %> diff --git a/app/views/cryptos/new.html.erb b/app/views/cryptos/new.html.erb index a803df7b..8e93242f 100644 --- a/app/views/cryptos/new.html.erb +++ b/app/views/cryptos/new.html.erb @@ -1,7 +1,7 @@ <% if params[:step] == "method_select" %> - <%= render "accounts/new/method_selector", - path: new_crypto_path(return_to: params[:return_to]), - show_us_link: @show_us_link, + <%= render "accounts/new/method_selector", + path: new_crypto_path(return_to: params[:return_to]), + show_us_link: @show_us_link, show_eu_link: @show_eu_link, accountable_type: "Crypto" %> <% else %> diff --git a/app/views/depositories/new.html.erb b/app/views/depositories/new.html.erb index c2110726..23bbd79b 100644 --- a/app/views/depositories/new.html.erb +++ b/app/views/depositories/new.html.erb @@ -1,7 +1,7 @@ <% if params[:step] == "method_select" %> - <%= render "accounts/new/method_selector", - path: new_depository_path(return_to: params[:return_to]), - show_us_link: @show_us_link, + <%= render "accounts/new/method_selector", + path: new_depository_path(return_to: params[:return_to]), + show_us_link: @show_us_link, show_eu_link: @show_eu_link, accountable_type: "Depository" %> <% else %> diff --git a/app/views/imports/_table.html.erb b/app/views/imports/_table.html.erb index 5cc3a49c..8de7cea3 100644 --- a/app/views/imports/_table.html.erb +++ b/app/views/imports/_table.html.erb @@ -14,7 +14,7 @@ <% headers.each_with_index do |header, index| %> <%= index == headers.length - 1 ? "rounded-tr-lg" : "" %> <%= index < headers.length - 1 ? "border-r border-r-alpha-black-200" : "" %> diff --git a/app/views/investments/new.html.erb b/app/views/investments/new.html.erb index d31af9c9..1c2fb8e4 100644 --- a/app/views/investments/new.html.erb +++ b/app/views/investments/new.html.erb @@ -1,7 +1,7 @@ <% if params[:step] == "method_select" %> - <%= render "accounts/new/method_selector", - path: new_investment_path(return_to: params[:return_to]), - show_us_link: @show_us_link, + <%= render "accounts/new/method_selector", + path: new_investment_path(return_to: params[:return_to]), + show_us_link: @show_us_link, show_eu_link: @show_eu_link, accountable_type: "Investment" %> <% else %> diff --git a/app/views/layouts/blank.html.erb b/app/views/layouts/blank.html.erb new file mode 100644 index 00000000..7193dcb6 --- /dev/null +++ b/app/views/layouts/blank.html.erb @@ -0,0 +1,13 @@ +<%= render "layouts/shared/htmldoc" do %> +
+
+ <%= yield %> +
+ + <% if content_for?(:footer) %> + <%= yield :footer %> + <% else %> + <%= render "layouts/shared/footer" %> + <% end %> +
+<% end %> diff --git a/app/views/loans/new.html.erb b/app/views/loans/new.html.erb index ae16df8e..c3784ffd 100644 --- a/app/views/loans/new.html.erb +++ b/app/views/loans/new.html.erb @@ -1,7 +1,7 @@ <% if params[:step] == "method_select" %> - <%= render "accounts/new/method_selector", - path: new_loan_path(return_to: params[:return_to]), - show_us_link: @show_us_link, + <%= render "accounts/new/method_selector", + path: new_loan_path(return_to: params[:return_to]), + show_us_link: @show_us_link, show_eu_link: @show_eu_link, accountable_type: "Loan" %> <% else %> diff --git a/app/views/pages/dashboard/_balance_sheet.html.erb b/app/views/pages/dashboard/_balance_sheet.html.erb index 337e5268..62a5077a 100644 --- a/app/views/pages/dashboard/_balance_sheet.html.erb +++ b/app/views/pages/dashboard/_balance_sheet.html.erb @@ -116,7 +116,7 @@ <% else %>
- <% + <% # Calculate weight as percentage of classification total classification_total = classification_group.total_money.amount account_weight = classification_total.zero? ? 0 : account.converted_balance / classification_total * 100 diff --git a/app/views/pages/redis_configuration_error.html.erb b/app/views/pages/redis_configuration_error.html.erb new file mode 100644 index 00000000..303ce4a7 --- /dev/null +++ b/app/views/pages/redis_configuration_error.html.erb @@ -0,0 +1,59 @@ +<% content_for :title, "Redis Configuration Required - Maybe" %> + +
+
+
+ +
+
+ <%= icon "alert-triangle", class: "w-8 h-8 text-red-600" %> +
+

Redis Configuration Required

+

Your self-hosted Maybe installation needs Redis to be properly configured.

+
+ + +
+
+
+ <%= icon "info", class: "w-5 h-5 text-amber-600 mt-0.5 mr-3 flex-shrink-0" %> +
+

Why is Redis required?

+

Maybe uses Redis to power Sidekiq background jobs for tasks like syncing account data, processing imports, and other background operations that keep your financial data up to date.

+
+
+
+ + +
+ <%= render LinkComponent.new( + text: "View Setup Guide", + href: "https://github.com/maybe-finance/maybe/blob/main/docs/hosting/docker.md", + variant: "primary", + size: "lg", + icon: "external-link", + full_width: true, + target: "_blank", + rel: "noopener noreferrer" + ) %> +

Follow our complete Docker setup guide to configure Redis

+
+
+ + +
+
+

Once you've configured Redis, refresh this page to continue.

+ <%= render ButtonComponent.new( + text: "Refresh Page", + variant: "secondary", + icon: "refresh-cw", + type: "button", + full_width: true, + onclick: "window.location.reload()" + ) %> +
+
+
+
+
diff --git a/app/views/plaid_items/_auto_link_opener.html.erb b/app/views/plaid_items/_auto_link_opener.html.erb index 0a43028b..7e9c7695 100644 --- a/app/views/plaid_items/_auto_link_opener.html.erb +++ b/app/views/plaid_items/_auto_link_opener.html.erb @@ -1,9 +1,9 @@ <%# locals: (link_token:, region:, item_id:, is_update: false) %> -<%= tag.div data: { - controller: "plaid", - plaid_link_token_value: link_token, +<%= tag.div data: { + controller: "plaid", + plaid_link_token_value: link_token, plaid_region_value: region, plaid_item_id_value: item_id, plaid_is_update_value: is_update - } %> \ No newline at end of file + } %> diff --git a/app/views/plaid_items/edit.html.erb b/app/views/plaid_items/edit.html.erb index 31620ad9..4830ad4e 100644 --- a/app/views/plaid_items/edit.html.erb +++ b/app/views/plaid_items/edit.html.erb @@ -1,8 +1,8 @@ <%# We render this in the empty modal frame so if Plaid flow is closed, user stays on same page they were on %> <%= turbo_frame_tag "modal" do %> - <%= render "plaid_items/auto_link_opener", - link_token: @link_token, - region: @plaid_item.plaid_region, - item_id: @plaid_item.id, + <%= render "plaid_items/auto_link_opener", + link_token: @link_token, + region: @plaid_item.plaid_region, + item_id: @plaid_item.id, is_update: true %> -<% end %> \ No newline at end of file +<% end %> diff --git a/app/views/plaid_items/new.html.erb b/app/views/plaid_items/new.html.erb index a3d83660..b927d375 100644 --- a/app/views/plaid_items/new.html.erb +++ b/app/views/plaid_items/new.html.erb @@ -1,8 +1,8 @@ <%# We render this in the empty modal frame so if Plaid flow is closed, user stays on same page they were on %> <%= turbo_frame_tag "modal" do %> - <%= render "plaid_items/auto_link_opener", - link_token: @link_token, - region: params[:region], - item_id: "", + <%= render "plaid_items/auto_link_opener", + link_token: @link_token, + region: params[:region], + item_id: "", is_update: false %> -<% end %> \ No newline at end of file +<% end %> diff --git a/config/routes.rb b/config/routes.rb index 35feaaf8..c2e8865e 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -205,6 +205,8 @@ Rails.application.routes.draw do post "stripe" end + get "redis-configuration-error", to: "pages#redis_configuration_error" + # Reveal health status on /up that returns 200 if the app boots with no exceptions, otherwise 500. # Can be used by load balancers and uptime monitors to verify that the app is live. get "up" => "rails/health#show", as: :rails_health_check