mirror of
https://github.com/maybe-finance/maybe.git
synced 2025-07-18 20:59:39 +02:00
Redis check for self hosted apps (#2353)
* Redis check for self hosted apps * Run linter with autocorrect * Add Redis to CI
This commit is contained in:
parent
4044a8519f
commit
9fabcf4c72
20 changed files with 152 additions and 47 deletions
7
.github/workflows/ci.yml
vendored
7
.github/workflows/ci.yml
vendored
|
@ -80,6 +80,7 @@ jobs:
|
||||||
PLAID_CLIENT_ID: foo
|
PLAID_CLIENT_ID: foo
|
||||||
PLAID_SECRET: bar
|
PLAID_SECRET: bar
|
||||||
DATABASE_URL: postgres://postgres:postgres@localhost:5432
|
DATABASE_URL: postgres://postgres:postgres@localhost:5432
|
||||||
|
REDIS_URL: redis://localhost:6379
|
||||||
RAILS_ENV: test
|
RAILS_ENV: test
|
||||||
|
|
||||||
services:
|
services:
|
||||||
|
@ -92,6 +93,12 @@ jobs:
|
||||||
- 5432:5432
|
- 5432:5432
|
||||||
options: --health-cmd="pg_isready" --health-interval=10s --health-timeout=5s --health-retries=3
|
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:
|
steps:
|
||||||
- name: Install packages
|
- 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
|
run: sudo apt-get update && sudo apt-get install --no-install-recommends -y google-chrome-stable curl libvips postgresql-client libpq-dev
|
||||||
|
|
|
@ -3,6 +3,8 @@ module SelfHostable
|
||||||
|
|
||||||
included do
|
included do
|
||||||
helper_method :self_hosted?, :self_hosted_first_login?
|
helper_method :self_hosted?, :self_hosted_first_login?
|
||||||
|
|
||||||
|
prepend_before_action :verify_self_host_config
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -13,4 +15,29 @@ module SelfHostable
|
||||||
def self_hosted_first_login?
|
def self_hosted_first_login?
|
||||||
self_hosted? && User.count.zero?
|
self_hosted? && User.count.zero?
|
||||||
end
|
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
|
end
|
||||||
|
|
|
@ -1,7 +1,8 @@
|
||||||
class PagesController < ApplicationController
|
class PagesController < ApplicationController
|
||||||
skip_before_action :authenticate_user!, only: %i[early_access]
|
|
||||||
include Periodable
|
include Periodable
|
||||||
|
|
||||||
|
skip_authentication only: :redis_configuration_error
|
||||||
|
|
||||||
def dashboard
|
def dashboard
|
||||||
@balance_sheet = Current.family.balance_sheet
|
@balance_sheet = Current.family.balance_sheet
|
||||||
@accounts = Current.family.accounts.active.with_attached_logo
|
@accounts = Current.family.accounts.active.with_attached_logo
|
||||||
|
@ -47,12 +48,8 @@ class PagesController < ApplicationController
|
||||||
render layout: "settings"
|
render layout: "settings"
|
||||||
end
|
end
|
||||||
|
|
||||||
def early_access
|
def redis_configuration_error
|
||||||
redirect_to root_path if self_hosted?
|
render layout: "blank"
|
||||||
|
|
||||||
@invite_codes_count = InviteCode.count
|
|
||||||
@invite_code = InviteCode.order("RANDOM()").limit(1).first
|
|
||||||
render layout: false
|
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="font-mono text-right text-xs text-warning">Error</p>
|
<p class="font-mono text-right text-xs text-warning">Error</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -5,4 +5,4 @@
|
||||||
</div>
|
</div>
|
||||||
<p class="font-mono text-right text-xs text-warning">Error</p>
|
<p class="font-mono text-right text-xs text-warning">Error</p>
|
||||||
</div>
|
</div>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
|
|
||||||
<% if show_us_link %>
|
<% if show_us_link %>
|
||||||
<%# Default US-only 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",
|
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 %>
|
data: { turbo_frame: "modal" } do %>
|
||||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||||
|
@ -23,7 +23,7 @@
|
||||||
|
|
||||||
<%# EU Link %>
|
<%# EU Link %>
|
||||||
<% if show_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",
|
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 %>
|
data: { turbo_frame: "modal" } do %>
|
||||||
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
<span class="flex w-8 h-8 shrink-0 grow-0 items-center justify-center rounded-lg bg-alpha-black-50 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.02)]">
|
||||||
|
|
|
@ -13,12 +13,12 @@
|
||||||
|
|
||||||
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
|
<%= render MenuComponent.new(icon_vertical: true) do |menu| %>
|
||||||
<% menu.with_item(
|
<% menu.with_item(
|
||||||
variant: "link",
|
variant: "link",
|
||||||
text: "Edit chat title",
|
text: "Edit chat title",
|
||||||
href: edit_chat_path(chat, ctx: "list"),
|
href: edit_chat_path(chat, ctx: "list"),
|
||||||
icon: "pencil",
|
icon: "pencil",
|
||||||
frame: dom_id(chat, "title")) %>
|
frame: dom_id(chat, "title")) %>
|
||||||
|
|
||||||
<% menu.with_item(
|
<% menu.with_item(
|
||||||
variant: "button",
|
variant: "button",
|
||||||
text: "Delete chat",
|
text: "Delete chat",
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<% if params[:step] == "method_select" %>
|
<% if params[:step] == "method_select" %>
|
||||||
<%= render "accounts/new/method_selector",
|
<%= render "accounts/new/method_selector",
|
||||||
path: new_credit_card_path(return_to: params[:return_to]),
|
path: new_credit_card_path(return_to: params[:return_to]),
|
||||||
show_us_link: @show_us_link,
|
show_us_link: @show_us_link,
|
||||||
show_eu_link: @show_eu_link,
|
show_eu_link: @show_eu_link,
|
||||||
accountable_type: "CreditCard" %>
|
accountable_type: "CreditCard" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<% if params[:step] == "method_select" %>
|
<% if params[:step] == "method_select" %>
|
||||||
<%= render "accounts/new/method_selector",
|
<%= render "accounts/new/method_selector",
|
||||||
path: new_crypto_path(return_to: params[:return_to]),
|
path: new_crypto_path(return_to: params[:return_to]),
|
||||||
show_us_link: @show_us_link,
|
show_us_link: @show_us_link,
|
||||||
show_eu_link: @show_eu_link,
|
show_eu_link: @show_eu_link,
|
||||||
accountable_type: "Crypto" %>
|
accountable_type: "Crypto" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<% if params[:step] == "method_select" %>
|
<% if params[:step] == "method_select" %>
|
||||||
<%= render "accounts/new/method_selector",
|
<%= render "accounts/new/method_selector",
|
||||||
path: new_depository_path(return_to: params[:return_to]),
|
path: new_depository_path(return_to: params[:return_to]),
|
||||||
show_us_link: @show_us_link,
|
show_us_link: @show_us_link,
|
||||||
show_eu_link: @show_eu_link,
|
show_eu_link: @show_eu_link,
|
||||||
accountable_type: "Depository" %>
|
accountable_type: "Depository" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<tr>
|
<tr>
|
||||||
<% headers.each_with_index do |header, index| %>
|
<% headers.each_with_index do |header, index| %>
|
||||||
<th class="
|
<th class="
|
||||||
bg-container-inset px-3 py-2 font-medium border-b border-b-alpha-black-200 text-left whitespace-nowrap
|
bg-container-inset px-3 py-2 font-medium border-b border-b-alpha-black-200 text-left whitespace-nowrap
|
||||||
<%= index == 0 ? "rounded-tl-lg" : "" %>
|
<%= index == 0 ? "rounded-tl-lg" : "" %>
|
||||||
<%= index == headers.length - 1 ? "rounded-tr-lg" : "" %>
|
<%= index == headers.length - 1 ? "rounded-tr-lg" : "" %>
|
||||||
<%= index < headers.length - 1 ? "border-r border-r-alpha-black-200" : "" %>
|
<%= index < headers.length - 1 ? "border-r border-r-alpha-black-200" : "" %>
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
<% if params[:step] == "method_select" %>
|
<% if params[:step] == "method_select" %>
|
||||||
<%= render "accounts/new/method_selector",
|
<%= render "accounts/new/method_selector",
|
||||||
path: new_investment_path(return_to: params[:return_to]),
|
path: new_investment_path(return_to: params[:return_to]),
|
||||||
show_us_link: @show_us_link,
|
show_us_link: @show_us_link,
|
||||||
show_eu_link: @show_eu_link,
|
show_eu_link: @show_eu_link,
|
||||||
accountable_type: "Investment" %>
|
accountable_type: "Investment" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
13
app/views/layouts/blank.html.erb
Normal file
13
app/views/layouts/blank.html.erb
Normal file
|
@ -0,0 +1,13 @@
|
||||||
|
<%= render "layouts/shared/htmldoc" do %>
|
||||||
|
<div class="min-h-screen bg-surface text-primary font-medium flex flex-col">
|
||||||
|
<div class="flex-1">
|
||||||
|
<%= yield %>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<% if content_for?(:footer) %>
|
||||||
|
<%= yield :footer %>
|
||||||
|
<% else %>
|
||||||
|
<%= render "layouts/shared/footer" %>
|
||||||
|
<% end %>
|
||||||
|
</div>
|
||||||
|
<% end %>
|
|
@ -1,7 +1,7 @@
|
||||||
<% if params[:step] == "method_select" %>
|
<% if params[:step] == "method_select" %>
|
||||||
<%= render "accounts/new/method_selector",
|
<%= render "accounts/new/method_selector",
|
||||||
path: new_loan_path(return_to: params[:return_to]),
|
path: new_loan_path(return_to: params[:return_to]),
|
||||||
show_us_link: @show_us_link,
|
show_us_link: @show_us_link,
|
||||||
show_eu_link: @show_eu_link,
|
show_eu_link: @show_eu_link,
|
||||||
accountable_type: "Loan" %>
|
accountable_type: "Loan" %>
|
||||||
<% else %>
|
<% else %>
|
||||||
|
|
|
@ -116,7 +116,7 @@
|
||||||
<% else %>
|
<% else %>
|
||||||
<div class="ml-auto flex items-center text-right gap-6">
|
<div class="ml-auto flex items-center text-right gap-6">
|
||||||
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
|
<div class="w-28 shrink-0 flex items-center justify-end gap-2">
|
||||||
<%
|
<%
|
||||||
# Calculate weight as percentage of classification total
|
# Calculate weight as percentage of classification total
|
||||||
classification_total = classification_group.total_money.amount
|
classification_total = classification_group.total_money.amount
|
||||||
account_weight = classification_total.zero? ? 0 : account.converted_balance / classification_total * 100
|
account_weight = classification_total.zero? ? 0 : account.converted_balance / classification_total * 100
|
||||||
|
|
59
app/views/pages/redis_configuration_error.html.erb
Normal file
59
app/views/pages/redis_configuration_error.html.erb
Normal file
|
@ -0,0 +1,59 @@
|
||||||
|
<% content_for :title, "Redis Configuration Required - Maybe" %>
|
||||||
|
|
||||||
|
<div class="flex items-center justify-center h-full p-4 sm:p-6 lg:p-8">
|
||||||
|
<div class="w-full max-w-md sm:max-w-lg lg:max-w-2xl">
|
||||||
|
<div class="bg-container border border-primary rounded-xl p-6 sm:p-8 shadow-sm">
|
||||||
|
<!-- Icon and Header -->
|
||||||
|
<div class="text-center mb-8">
|
||||||
|
<div class="mx-auto w-16 h-16 bg-red-100 rounded-full flex items-center justify-center mb-4">
|
||||||
|
<%= icon "alert-triangle", class: "w-8 h-8 text-red-600" %>
|
||||||
|
</div>
|
||||||
|
<h1 class="text-xl sm:text-2xl font-bold text-primary mb-2">Redis Configuration Required</h1>
|
||||||
|
<p class="text-sm sm:text-base text-muted">Your self-hosted Maybe installation needs Redis to be properly configured.</p>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Explanation -->
|
||||||
|
<div class="mb-8">
|
||||||
|
<div class="bg-amber-50 border border-amber-200 rounded-lg p-4 mb-6">
|
||||||
|
<div class="flex items-start">
|
||||||
|
<%= icon "info", class: "w-5 h-5 text-amber-600 mt-0.5 mr-3 flex-shrink-0" %>
|
||||||
|
<div class="text-sm text-amber-800">
|
||||||
|
<p><strong>Why is Redis required?</strong></p>
|
||||||
|
<p class="mt-1">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.</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Primary CTA -->
|
||||||
|
<div class="text-center space-y-4">
|
||||||
|
<%= 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"
|
||||||
|
) %>
|
||||||
|
<p class="text-sm text-muted">Follow our complete Docker setup guide to configure Redis</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<!-- Secondary CTA -->
|
||||||
|
<div class="pt-6 border-t border-primary">
|
||||||
|
<div class="text-center space-y-3">
|
||||||
|
<p class="text-sm text-muted">Once you've configured Redis, refresh this page to continue.</p>
|
||||||
|
<%= render ButtonComponent.new(
|
||||||
|
text: "Refresh Page",
|
||||||
|
variant: "secondary",
|
||||||
|
icon: "refresh-cw",
|
||||||
|
type: "button",
|
||||||
|
full_width: true,
|
||||||
|
onclick: "window.location.reload()"
|
||||||
|
) %>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>
|
|
@ -1,9 +1,9 @@
|
||||||
<%# locals: (link_token:, region:, item_id:, is_update: false) %>
|
<%# locals: (link_token:, region:, item_id:, is_update: false) %>
|
||||||
|
|
||||||
<%= tag.div data: {
|
<%= tag.div data: {
|
||||||
controller: "plaid",
|
controller: "plaid",
|
||||||
plaid_link_token_value: link_token,
|
plaid_link_token_value: link_token,
|
||||||
plaid_region_value: region,
|
plaid_region_value: region,
|
||||||
plaid_item_id_value: item_id,
|
plaid_item_id_value: item_id,
|
||||||
plaid_is_update_value: is_update
|
plaid_is_update_value: is_update
|
||||||
} %>
|
} %>
|
||||||
|
|
|
@ -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 %>
|
<%# 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 %>
|
<%= turbo_frame_tag "modal" do %>
|
||||||
<%= render "plaid_items/auto_link_opener",
|
<%= render "plaid_items/auto_link_opener",
|
||||||
link_token: @link_token,
|
link_token: @link_token,
|
||||||
region: @plaid_item.plaid_region,
|
region: @plaid_item.plaid_region,
|
||||||
item_id: @plaid_item.id,
|
item_id: @plaid_item.id,
|
||||||
is_update: true %>
|
is_update: true %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -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 %>
|
<%# 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 %>
|
<%= turbo_frame_tag "modal" do %>
|
||||||
<%= render "plaid_items/auto_link_opener",
|
<%= render "plaid_items/auto_link_opener",
|
||||||
link_token: @link_token,
|
link_token: @link_token,
|
||||||
region: params[:region],
|
region: params[:region],
|
||||||
item_id: "",
|
item_id: "",
|
||||||
is_update: false %>
|
is_update: false %>
|
||||||
<% end %>
|
<% end %>
|
||||||
|
|
|
@ -205,6 +205,8 @@ Rails.application.routes.draw do
|
||||||
post "stripe"
|
post "stripe"
|
||||||
end
|
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.
|
# 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.
|
# 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
|
get "up" => "rails/health#show", as: :rails_health_check
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue