Donovan Daniels
b1c702e3cd
poorly tested but it worked well enough, I'm sure I'll be patching bugs over the next few weeks Also remove turbo because it sucks Also changed the way we handle hosts in dev
258 lines
7.3 KiB
Ruby
258 lines
7.3 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
class ApplicationController < ActionController::Base
|
|
class ReadOnlyError < StandardError; end
|
|
class FeatureUnavailableError < StandardError; end
|
|
|
|
before_action :set_view_path
|
|
before_action :initialize_session
|
|
before_action :normalize_search
|
|
before_action :set_common_headers
|
|
helper_method :site_domain, :assets_path, :safe_site_name, :site_title, :site_color, :controller_param, :action_param, :body_class, :stimulus_class
|
|
skip_before_action :verify_authenticity_token
|
|
|
|
rescue_from Exception, with: :rescue_exception
|
|
|
|
def set_view_path
|
|
prepend_view_path(Rails.root.join("app", "views", safe_site_name))
|
|
end
|
|
|
|
def set_common_headers
|
|
Websites.config.common_headers(request.domain).each do |key, value|
|
|
response.headers[key] = value
|
|
end
|
|
end
|
|
|
|
def initialize_session
|
|
WebLogger.initialize(request)
|
|
CurrentUser.user = APIUser.anonymous
|
|
CurrentUser.ip_addr = request.remote_ip
|
|
end
|
|
|
|
def site_domain
|
|
"unknown"
|
|
end
|
|
|
|
def assets_path
|
|
site_domain
|
|
end
|
|
|
|
def site_title
|
|
"Unknown"
|
|
end
|
|
|
|
def site_color
|
|
"#2C2F33"
|
|
end
|
|
|
|
def safe_site_name
|
|
site_domain.parameterize.dasherize
|
|
end
|
|
|
|
def controller_param
|
|
return "unknown" unless params[:controller]
|
|
name = params[:controller].parameterize.dasherize
|
|
name = name.gsub("#{safe_site_name}-", "") if name.include?(safe_site_name)
|
|
name
|
|
end
|
|
|
|
def action_param
|
|
return "unknown" unless params[:action]
|
|
params[:action]
|
|
end
|
|
|
|
def body_class
|
|
"s-#{safe_site_name} c-#{controller_param} a-#{action_param}"
|
|
end
|
|
|
|
def stimulus_class
|
|
params[:controller].gsub("/", "--").dasherize
|
|
end
|
|
|
|
def track_usage(service)
|
|
APIUsage.create!(
|
|
user_id: CurrentUser.is_anonymous? ? nil : CurrentUser.id,
|
|
api_key: @apikey.nil? || @apikey.is_anon? ? nil : @apikey,
|
|
user_agent: request.headers["user-agent"],
|
|
method: request.method,
|
|
path: request.path,
|
|
params: request.params.to_json,
|
|
service: service,
|
|
ip_addr: request.remote_ip,
|
|
)
|
|
end
|
|
|
|
module CommonAssetRoutes
|
|
def manifest
|
|
render(partial: "manifest", layout: false)
|
|
end
|
|
|
|
def browserconfig
|
|
render(partial: "browserconfig", layout: false)
|
|
end
|
|
end
|
|
|
|
module CommonRoutes
|
|
def online
|
|
render(json: {
|
|
success: true,
|
|
uptime: Time.now - Websites::STARTED_AT,
|
|
})
|
|
end
|
|
|
|
def access_denied(message: nil, code: nil)
|
|
@message = message.present? ? "Access Denied: #{message}" : "Access Denied"
|
|
@code = code
|
|
respond_to do |fmt|
|
|
fmt.html { render("static/access_denied", status: 403) }
|
|
fmt.json do
|
|
render(json: {
|
|
success: false,
|
|
message: @message,
|
|
code: @code,
|
|
}, status: 403)
|
|
end
|
|
end
|
|
end
|
|
|
|
def not_found(code: nil)
|
|
@code = code
|
|
respond_to do |fmt|
|
|
fmt.any { render("static/not_found", status: 404) }
|
|
fmt.json do
|
|
render(json: {
|
|
success: false,
|
|
message: "Not found.",
|
|
code: @code,
|
|
}, status: 404)
|
|
end
|
|
end
|
|
end
|
|
|
|
def readonly
|
|
respond_to do |fmt|
|
|
fmt.html { render("static/readonly", status: YiffyAPIErrorCodes::READONLY.status) }
|
|
fmt.json { render_error(YiffyAPIErrorCodes::READONLY, message: "This service is currently in read only mode. Try again later.") }
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def render_error(*, **)
|
|
extend(YiffyAPIUtil).render_error(*, **)
|
|
end
|
|
end
|
|
|
|
module RenderMethods
|
|
def handle_error
|
|
@exception = request.env["action_dispatch.exception"]
|
|
@status_code = @exception.try(:status_code) ||
|
|
ActionDispatch::ExceptionWrapper.new(
|
|
request.env, @exception
|
|
).status_code
|
|
return not_found if @status_code == 404
|
|
return rescue_exception(@exception) if @exception
|
|
render_error_page(@status_code, Exception.new("An unexpected error occurred."))
|
|
end
|
|
|
|
def rescue_exception(exception)
|
|
@exception = exception
|
|
case exception
|
|
when ActiveRecord::RecordNotFound
|
|
not_found
|
|
when ReadOnlyError
|
|
readonly
|
|
when PG::Error
|
|
render_error_page(503, exception, message: "The database is unavailable. Try again later.")
|
|
when ActionController::ParameterMissing, ActionController::UnpermittedParameters
|
|
render_expected_error(400, exception.message)
|
|
when ActionController::RoutingError
|
|
render_error_page(405, exception)
|
|
when ActionController::UnknownFormat, ActionView::MissingTemplate
|
|
render_unsupported_format
|
|
when ActionController::InvalidAuthenticityToken
|
|
access_denied(message: "Invalid CSRF Token")
|
|
else
|
|
render_error_page(500, exception)
|
|
end
|
|
end
|
|
|
|
def render_unsupported_format
|
|
return not_found if request.format.nil?
|
|
render_expected_error(406, "#{request.format} is not a supported format for this page", format: :html)
|
|
end
|
|
|
|
def render_expected_error(status, message, format: request.format.symbol)
|
|
format = :html unless format.in?(%i[html json])
|
|
@message = message
|
|
@log_code = nil
|
|
render("static/error", status: status, formats: format)
|
|
end
|
|
|
|
def render_error_page(status, exception, message: exception.message, format: request.format.symbol)
|
|
@exception = exception
|
|
@expected = status < 500
|
|
@message = message.encode("utf-8", invalid: :replace, undef: :replace)
|
|
@backtrace = Rails.backtrace_cleaner.clean(@exception.backtrace)
|
|
format = :html unless format.in?(%i[html json])
|
|
|
|
@message = "An unexpected error occurred." if !(Rails.env.development? || CurrentUser.is_admin?) && message == exception.message
|
|
|
|
WebLogger.log_exception(@exception, expected: @expected)
|
|
log = ExceptionLog.add(exception, request) unless @expected
|
|
@log_code = log&.code
|
|
render("static/error", status: status, formats: format)
|
|
end
|
|
end
|
|
|
|
module SearchMethods
|
|
def normalize_search
|
|
return unless request.get? || request.head?
|
|
params[:search] ||= ActionController::Parameters.new
|
|
|
|
deep_reject_blank = ->(hash) do
|
|
hash.reject { |_k, v| v.blank? || (v.is_a?(Hash) && deep_reject_blank.call(v).blank?) }
|
|
end
|
|
if params[:search].is_a?(ActionController::Parameters)
|
|
nonblank_search_params = deep_reject_blank.call(params[:search])
|
|
else
|
|
nonblank_search_params = ActionController::Parameters.new
|
|
end
|
|
|
|
if nonblank_search_params != params[:search]
|
|
params[:search] = nonblank_search_params
|
|
redirect_to(url_for(params: params.except(:controller, :action, :index).permit!))
|
|
end
|
|
end
|
|
|
|
def search_params
|
|
params.fetch(:search, {}).permit!
|
|
end
|
|
|
|
def permit_search_params(permitted_params)
|
|
params.fetch(:search, {}).permit(%i[id created_at updated_at] + permitted_params)
|
|
end
|
|
end
|
|
|
|
module ReadonlyMethods
|
|
extend ActiveSupport::Concern
|
|
def enforce_readonly
|
|
return unless Websites.config.readonly?
|
|
raise(ReadOnlyError) unless allowed_readonly_actions.include?(action_name)
|
|
end
|
|
|
|
def allowed_readonly_actions
|
|
%w[index show]
|
|
end
|
|
|
|
included do
|
|
before_action :enforce_readonly
|
|
end
|
|
end
|
|
|
|
include CommonRoutes
|
|
include RenderMethods
|
|
include SearchMethods
|
|
include Pagy::Backend
|
|
end
|