diff --git a/app/controllers/errors_controller.rb b/app/controllers/errors_controller.rb index 830b784..24ef791 100644 --- a/app/controllers/errors_controller.rb +++ b/app/controllers/errors_controller.rb @@ -2,6 +2,7 @@ class ErrorsController < ApplicationController skip_before_action :verify_authenticity_token + before_action :render_maintenance_page, except: :not_found def bad_request @status_code = 400 @@ -32,8 +33,21 @@ class ErrorsController < ApplicationController render_error end + def service_unavailable + @status_code = 503 + @title = "Service Unavailable" + @message = "The service is temporarily unavailable. Please try again later." + render_error + end + private + def render_maintenance_page + return unless ENV["MAINTENANCE_MODE"].present? + + render file: Rails.root.join("public", "maintenance.html"), layout: false, status: 503, content_type: "text/html" + end + def render_error respond_to do |format| format.html { render "errors/show", status: @status_code, layout: error_layout } diff --git a/app/controllers/maintenance_controller.rb b/app/controllers/maintenance_controller.rb new file mode 100644 index 0000000..9efe0c2 --- /dev/null +++ b/app/controllers/maintenance_controller.rb @@ -0,0 +1,7 @@ +class MaintenanceController < ApplicationController + skip_before_action :enforce_lockout + + def show + render file: Rails.root.join("public", "maintenance.html"), layout: false, content_type: "text/html" + end +end diff --git a/config/application.rb b/config/application.rb index a5a9930..d557751 100644 --- a/config/application.rb +++ b/config/application.rb @@ -57,6 +57,9 @@ module Harbor secure: Rails.env.production?, httponly: true + require_relative "../lib/maintenance_mode_middleware" + config.middleware.insert_before 0, MaintenanceModeMiddleware + config.middleware.use Rack::Attack config.exceptions_app = routes end diff --git a/config/routes.rb b/config/routes.rb index 4676bf9..8d70040 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -80,6 +80,9 @@ Rails.application.routes.draw do mount LetterOpenerWeb::Engine, at: "/letter_opener" end + # Maintenance page (always accessible for previewing) + get "maintenance", to: "maintenance#show" + # 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 @@ -329,4 +332,5 @@ Rails.application.routes.draw do match "/404", to: "errors#not_found", via: :all match "/422", to: "errors#unprocessable_entity", via: :all match "/500", to: "errors#internal_server_error", via: :all + match "/503", to: "errors#service_unavailable", via: :all end diff --git a/lib/maintenance_mode_middleware.rb b/lib/maintenance_mode_middleware.rb new file mode 100644 index 0000000..609b684 --- /dev/null +++ b/lib/maintenance_mode_middleware.rb @@ -0,0 +1,17 @@ +class MaintenanceModeMiddleware + MAINTENANCE_PAGE = Rails.root.join("public", "maintenance.html").freeze + SKIP_PATHS = %w[/up /maintenance].freeze + + def initialize(app) + @app = app + end + + def call(env) + if ENV["MAINTENANCE_MODE"].present? && !SKIP_PATHS.include?(env["PATH_INFO"]) + content = File.read(MAINTENANCE_PAGE) + [ 503, { "Content-Type" => "text/html", "Retry-After" => "1200" }, [ content ] ] + else + @app.call(env) + end + end +end diff --git a/public/maintenance.html b/public/maintenance.html new file mode 100644 index 0000000..3eef63b --- /dev/null +++ b/public/maintenance.html @@ -0,0 +1,188 @@ + + +
+ + ++ Yes. Once Hackatime comes back up, your editor extensions will + automatically reconnect to our servers and upload your tracked time. +
++ We are performing scheduled updates to improve infrastructure + reliability. We appreciate your patience during this brief downtime. +
+