about summary refs log tree commit diff
path: root/app/controllers/application_controller.rb
blob: 17c9dade8ab6a3225653b5fb6d0d07bc34ea4380 (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
# frozen_string_literal: true

class ApplicationController < ActionController::Base
  # Prevent CSRF attacks by raising an exception.
  # For APIs, you may want to use :null_session instead.
  protect_from_forgery with: :exception

  force_ssl if: :https_enabled?

  include Localized
  include UserTrackingConcern

  helper_method :current_account
  helper_method :current_session
  helper_method :current_theme
  helper_method :single_user_mode?
  helper_method :use_pam?

  rescue_from ActionController::RoutingError, with: :not_found
  rescue_from ActiveRecord::RecordNotFound, with: :not_found
  rescue_from ActionController::InvalidAuthenticityToken, with: :unprocessable_entity
  rescue_from Mastodon::NotPermittedError, with: :forbidden

  before_action :store_current_location, except: :raise_not_found, unless: :devise_controller?
  before_action :check_suspension, if: :user_signed_in?

  def raise_not_found
    raise ActionController::RoutingError, "No route matches #{params[:unmatched_route]}"
  end

  private

  def https_enabled?
    Rails.env.production?
  end

  def store_current_location
    store_location_for(:user, request.url) unless request.format == :json
  end

  def require_admin!
    redirect_to root_path unless current_user&.admin?
  end

  def require_staff!
    redirect_to root_path unless current_user&.staff?
  end

  def check_suspension
    forbidden if current_user.account.suspended?
  end

  def after_sign_out_path_for(_resource_or_scope)
    new_user_session_path
  end

  protected

  def forbidden
    respond_with_error(403)
  end

  def not_found
    respond_with_error(404)
  end

  def gone
    respond_with_error(410)
  end

  def unprocessable_entity
    respond_with_error(422)
  end

  def single_user_mode?
    @single_user_mode ||= Rails.configuration.x.single_user_mode && Account.exists?
  end

  def use_pam?
    Devise.pam_authentication
  end

  def current_account
    @current_account ||= current_user.try(:account)
  end

  def current_session
    @current_session ||= SessionActivation.find_by(session_id: cookies.signed['_session_id'])
  end

  def current_theme
    return Setting.default_settings['theme'] unless Themes.instance.names.include? current_user&.setting_theme
    current_user.setting_theme
  end

  def cache_collection(raw, klass)
    return raw unless klass.respond_to?(:with_includes)

    raw                    = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
    uncached_ids           = []
    cached_keys_with_value = Rails.cache.read_multi(*raw.map(&:cache_key))

    raw.each do |item|
      uncached_ids << item.id unless cached_keys_with_value.key?(item.cache_key)
    end

    klass.reload_stale_associations!(cached_keys_with_value.values) if klass.respond_to?(:reload_stale_associations!)

    unless uncached_ids.empty?
      uncached = klass.where(id: uncached_ids).with_includes.map { |item| [item.id, item] }.to_h

      uncached.each_value do |item|
        Rails.cache.write(item.cache_key, item)
      end
    end

    raw.map { |item| cached_keys_with_value[item.cache_key] || uncached[item.id] }.compact
  end

  def respond_with_error(code)
    respond_to do |format|
      format.any  { head code }
      format.html do
        set_locale
        render "errors/#{code}", layout: 'error', status: code
      end
    end
  end

  def render_cached_json(cache_key, **options)
    options[:expires_in] ||= 3.minutes
    cache_key              = cache_key.join(':') if cache_key.is_a?(Enumerable)
    cache_public           = options.key?(:public) ? options.delete(:public) : true
    content_type           = options.delete(:content_type) || 'application/json'

    data = Rails.cache.fetch(cache_key, { raw: true }.merge(options)) do
      yield.to_json
    end

    expires_in options[:expires_in], public: cache_public
    render json: data, content_type: content_type
  end

  def set_cache_headers
    response.headers['Vary'] = 'Accept'
  end

  def skip_session!
    request.session_options[:skip] = true
  end
end