2013-02-05 14:16:51 -05:00
require 'current_user'
2013-02-13 19:04:43 +08:00
require 'canonical_url'
2013-02-05 14:16:51 -05:00
require_dependency 'discourse'
require_dependency 'custom_renderer'
require 'archetype'
require_dependency 'rate_limiter'
class ApplicationController < ActionController :: Base
include CurrentUser
2013-02-13 19:04:43 +08:00
include CanonicalURL :: ControllerExtensions
2013-02-05 14:16:51 -05:00
serialization_scope :guardian
protect_from_forgery
before_filter :inject_preview_style
before_filter :block_if_maintenance_mode
before_filter :check_restricted_access
before_filter :authorize_mini_profiler
before_filter :store_incoming_links
before_filter :preload_json
before_filter :check_xhr
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
rescue_from Exception do | exception |
2013-02-07 16:45:24 +01:00
unless [ ActiveRecord :: RecordNotFound , ActionController :: RoutingError ,
2013-02-05 14:16:51 -05:00
ActionController :: UnknownController , AbstractController :: ActionNotFound ] . include? exception . class
2013-02-07 16:45:24 +01:00
begin
2013-02-05 14:16:51 -05:00
ErrorLog . report_async! ( exception , self , request , current_user )
2013-02-07 16:45:24 +01:00
rescue
2013-02-05 14:16:51 -05:00
# dont care give up
end
end
raise
end
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
# Some exceptions
class RenderEmpty < Exception ; end
class NotLoggedIn < Exception ; end
# Render nothing unless we are an xhr request
rescue_from RenderEmpty do
render 'default/empty'
end
# If they hit the rate limiter
rescue_from RateLimiter :: LimitExceeded do | e |
time_left = " "
if e . available_in < 1 . minute . to_i
time_left = I18n . t ( " rate_limiter.seconds " , count : e . available_in )
elsif e . available_in < 1 . hour . to_i
2013-02-07 16:45:24 +01:00
time_left = I18n . t ( " rate_limiter.minutes " , count : ( e . available_in / 1 . minute . to_i ) )
2013-02-05 14:16:51 -05:00
else
2013-02-07 16:45:24 +01:00
time_left = I18n . t ( " rate_limiter.hours " , count : ( e . available_in / 1 . hour . to_i ) )
2013-02-05 14:16:51 -05:00
end
render json : { errors : [ I18n . t ( " rate_limiter.too_many_requests " , time_left : time_left ) ] } , status : 429
end
rescue_from Discourse :: NotLoggedIn do | e |
raise e if Rails . env . test?
redirect_to root_path
end
rescue_from Discourse :: NotFound do
if request . format . html?
# for now do a simple remap, we may look at cleaner ways of doing the render
raise ActiveRecord :: RecordNotFound
else
2013-02-11 15:47:28 -05:00
render file : 'public/404' , formats : [ :html ] , layout : false , status : 404
2013-02-05 14:16:51 -05:00
end
end
rescue_from Discourse :: InvalidAccess do
2013-02-11 15:47:28 -05:00
render file : 'public/403' , formats : [ :html ] , layout : false , status : 403
2013-02-05 14:16:51 -05:00
end
def store_preloaded ( key , json )
@preloaded || = { }
2013-02-11 17:28:21 +11:00
# I dislike that there is a gsub as opposed to a gsub!
# but we can not be mucking with user input, I wonder if there is a way
# to inject this safty deeper in the library or even in AM serializer
@preloaded [ key ] = json . gsub ( " </ " , " < \\ / " )
2013-02-05 14:16:51 -05:00
end
# If we are rendering HTML, preload the session data
def preload_json
if request . format . html?
if guardian . current_user
guardian . current_user . sync_notification_channel_position
end
store_preloaded ( " site " , Site . cached_json )
if current_user . present?
store_preloaded ( " currentUser " , MultiJson . dump ( CurrentUserSerializer . new ( current_user , root : false ) ) )
end
store_preloaded ( " siteSettings " , SiteSetting . client_settings_json )
end
end
def inject_preview_style
style = request [ 'preview-style' ]
2013-02-07 16:45:24 +01:00
session [ :preview_style ] = style if style
2013-02-05 14:16:51 -05:00
end
def guardian
@guardian || = Guardian . new ( current_user )
end
def log_on_user ( user )
2013-02-07 16:45:24 +01:00
session [ :current_user_id ] = user . id
2013-02-05 14:16:51 -05:00
unless user . auth_token
user . auth_token = SecureRandom . hex ( 16 )
user . save!
end
2013-02-20 17:24:19 -05:00
cookies . permanent . signed [ :_t ] = { :value = > user . auth_token , :httponly = > true }
2013-02-05 14:16:51 -05:00
end
# This is odd, but it seems that in Rails `render json: obj` is about
# 20% slower than calling MultiJSON.dump ourselves. I'm not sure why
2013-02-07 16:45:24 +01:00
# Rails doesn't call MultiJson.dump when you pass it json: obj but
2013-02-05 14:16:51 -05:00
# it seems we don't need whatever Rails is doing.
def render_serialized ( obj , serializer , opts = { } )
# If it's an array, apply the serializer as an each_serializer to the elements
serializer_opts = { scope : guardian } . merge! ( opts )
if obj . is_a? ( Array )
serializer_opts [ :each_serializer ] = serializer
render_json_dump ( ActiveModel :: ArraySerializer . new ( obj , serializer_opts ) . as_json )
2013-02-07 16:45:24 +01:00
else
2013-02-05 14:16:51 -05:00
render_json_dump ( serializer . new ( obj , serializer_opts ) . as_json )
end
end
def render_json_dump ( obj )
render json : MultiJson . dump ( obj )
end
2013-02-06 11:55:54 -05:00
def can_cache_content?
# Don't cache unless we're in production mode
return false unless Rails . env . production?
# Don't cache logged in users
return false if current_user . present?
# Don't cache if there's restricted access
2013-02-07 16:45:24 +01:00
return false if SiteSetting . restrict_access?
2013-02-06 11:55:54 -05:00
true
end
# Our custom cache method
def discourse_expires_in ( time_length )
return unless can_cache_content?
expires_in time_length , public : true
end
2013-02-05 14:16:51 -05:00
# Helper method - if no logged in user (anonymous), use Rails' conditional GET
# support. Should be very fast behind a cache.
def anonymous_etag ( * args )
2013-02-06 11:55:54 -05:00
if can_cache_content?
2013-02-05 14:16:51 -05:00
yield if stale? ( * args )
# Add a one minute expiry
2013-02-06 12:07:22 -05:00
expires_in 1 . minute , public : true
2013-02-05 14:16:51 -05:00
else
yield
end
end
private
def render_json_error ( obj )
if obj . present?
render json : MultiJson . dump ( errors : obj . errors . full_messages ) , status : 422
else
render json : MultiJson . dump ( errors : [ I18n . t ( 'js.generic_error' ) ] ) , status : 422
end
end
def success_json
{ success : 'OK' }
end
def failed_json
{ failed : 'FAILED' }
end
def json_result ( obj , opts = { } )
if yield ( obj )
json = success_json
# If we were given a serializer, add the class to the json that comes back
if opts [ :serializer ] . present?
json [ obj . class . name . underscore ] = opts [ :serializer ] . new ( obj ) . serializable_hash
end
render json : MultiJson . dump ( json )
else
render_json_error ( obj )
end
2013-02-07 16:45:24 +01:00
end
2013-02-05 14:16:51 -05:00
def block_if_maintenance_mode
if Discourse . maintenance_mode?
if request . format . json?
render status : 503 , json : failed_json . merge ( message : 'Site is currently undergoing maintenance.' )
else
render status : 503 , file : File . join ( Rails . root , 'public' , '503.html' ) , layout : false
end
end
end
def check_restricted_access
2013-02-07 16:45:24 +01:00
# note current_user is defined in the CurrentUser mixin
2013-02-05 14:16:51 -05:00
if SiteSetting . restrict_access? && cookies [ :_access ] != SiteSetting . access_password
redirect_to request_access_path ( :return_path = > request . fullpath )
2013-02-07 16:45:24 +01:00
return false
end
2013-02-05 14:16:51 -05:00
end
def mini_profiler_enabled?
defined? ( Rack :: MiniProfiler ) and current_user . try ( :admin? )
end
def authorize_mini_profiler
return unless mini_profiler_enabled?
Rack :: MiniProfiler . authorize_request
end
def requires_parameters ( * required )
2013-02-07 16:45:24 +01:00
required . each do | p |
2013-02-05 14:16:51 -05:00
raise Discourse :: InvalidParameters . new ( p ) unless params . has_key? ( p )
end
end
alias :requires_parameter :requires_parameters
def store_incoming_links
if request . referer . present?
parsed = URI . parse ( request . referer )
2013-02-07 16:45:24 +01:00
if parsed . host != request . host
2013-02-05 14:16:51 -05:00
IncomingLink . create ( url : request . url , referer : request . referer [ 0 .. 999 ] )
end
end
end
2013-02-07 16:45:24 +01:00
def check_xhr
2013-02-05 14:16:51 -05:00
unless ( controller_name == 'forums' || controller_name == 'user_open_ids' )
# render 'default/empty' unless ((request.format && request.format.json?) or request.xhr?)
raise RenderEmpty . new unless ( ( request . format && request . format . json? ) or request . xhr? )
end
end
def ensure_logged_in
raise Discourse :: NotLoggedIn . new unless current_user . present?
end
2013-02-07 16:45:24 +01:00
2013-02-05 14:16:51 -05:00
end