From e7472dc3748e2c24e0ad6dfd3e34c6e2049deb38 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?R=C3=A9gis=20Hanol?= Date: Wed, 12 Feb 2014 20:37:28 -0800 Subject: [PATCH] readonly mode --- .../initializers/1_init_message_bus.js | 13 ++++++- app/controllers/application_controller.rb | 23 ++++++------- app/serializers/site_serializer.rb | 7 +++- config/site_settings.yml | 4 ++- lib/discourse.rb | 28 +++++++++------ spec/components/discourse_spec.rb | 34 +++++++++++++++++++ 6 files changed, 84 insertions(+), 25 deletions(-) diff --git a/app/assets/javascripts/discourse/initializers/1_init_message_bus.js b/app/assets/javascripts/discourse/initializers/1_init_message_bus.js index 04bce514c93..8d644b103c4 100644 --- a/app/assets/javascripts/discourse/initializers/1_init_message_bus.js +++ b/app/assets/javascripts/discourse/initializers/1_init_message_bus.js @@ -8,8 +8,9 @@ Discourse.addInitializer(function() { Discourse.MessageBus.alwaysLongPoll = Discourse.Environment === "development"; Discourse.MessageBus.start(); + Discourse.MessageBus.subscribe("/global/asset-version", function(version){ - Discourse.set("assetVersion",version); + Discourse.set("assetVersion", version); if(Discourse.get("requiresRefresh")) { // since we can do this transparently for people browsing the forum @@ -24,5 +25,15 @@ Discourse.addInitializer(function() { } }); + + Discourse.set("isReadOnly", Discourse.Site.currentProp("is_readonly")); + + Discourse.MessageBus.subscribe("/global/read-only", function (enabled) { + Discourse.set("isReadOnly", enabled); + if (enabled) { + bootbox.alert(I18n.t("read_only_mode_enabled")); + } + }); + Discourse.KeyValueStore.init("discourse_", Discourse.MessageBus); }, true); diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 234b0a7c5ba..3170aff4227 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -29,7 +29,7 @@ class ApplicationController < ActionController::Base before_filter :set_mobile_view before_filter :inject_preview_style before_filter :disable_customization - before_filter :block_if_maintenance_mode + before_filter :block_if_readonly_mode before_filter :authorize_mini_profiler before_filter :store_incoming_links before_filter :preload_json @@ -50,7 +50,6 @@ class ApplicationController < ActionController::Base raise end - # Some exceptions class RenderEmpty < Exception; end @@ -87,6 +86,11 @@ class ApplicationController < ActionController::Base rescue_discourse_actions("[error: 'invalid access']", 403) # TODO: this breaks json responses end + rescue_from Discourse::ReadOnly do + # can this happen on a not .json format? + render json: failed_json.merge(message: I18n.t("read_only_mode_enabled")) + end + def rescue_discourse_actions(message, error) if request.format && request.format.json? # TODO: this doesn't make sense. Stuffing an html page into a json response will cause @@ -249,16 +253,6 @@ class ApplicationController < ActionController::Base end end - def block_if_maintenance_mode - if Discourse.maintenance_mode? - if request.format.json? - render status: 503, json: failed_json.merge(message: I18n.t('site_under_maintenance')) - else - render status: 503, file: File.join( Rails.root, 'public', '503.html' ), layout: false - end - end - end - def mini_profiler_enabled? defined?(Rack::MiniProfiler) && current_user.try(:admin?) end @@ -288,6 +282,11 @@ class ApplicationController < ActionController::Base redirect_to :login if SiteSetting.login_required? end + def block_if_readonly_mode + return if request.put? && request.fullpath == "/admin/backups/readonly" + raise Discourse::ReadOnly.new unless request.get? || !Discourse.readonly_mode? + end + def build_not_found_page(status=404, layout=false) @top_viewed = Topic.top_viewed(10) @recent = Topic.recent(10) diff --git a/app/serializers/site_serializer.rb b/app/serializers/site_serializer.rb index c3953a8da09..0f2f1b6999d 100644 --- a/app/serializers/site_serializer.rb +++ b/app/serializers/site_serializer.rb @@ -8,7 +8,8 @@ class SiteSerializer < ApplicationSerializer :periods, :top_menu_items, :anonymous_top_menu_items, - :uncategorized_category_id # this is hidden so putting it here + :uncategorized_category_id, # this is hidden so putting it here + :is_readonly has_many :categories, serializer: BasicCategorySerializer, embed: :objects has_many :post_action_types, embed: :objects @@ -45,4 +46,8 @@ class SiteSerializer < ApplicationSerializer SiteSetting.uncategorized_category_id end + def is_readonly + Discourse.readonly_mode? + end + end diff --git a/config/site_settings.yml b/config/site_settings.yml index a88b1234d54..dd8e54f7af3 100644 --- a/config/site_settings.yml +++ b/config/site_settings.yml @@ -409,7 +409,9 @@ uncategorized: summary_likes_required: 1 summary_percent_filter: 20 send_welcome_message: true - allow_import: false + allow_import: + client: true + default: false educate_until_posts: client: true default: 2 diff --git a/lib/discourse.rb b/lib/discourse.rb index f6e02c7e2b9..1f67d21f9d4 100644 --- a/lib/discourse.rb +++ b/lib/discourse.rb @@ -27,6 +27,9 @@ module Discourse class InvalidPost < Exception; end + # When read-only mode is enabled + class ReadOnly < Exception; end + # Cross site request forgery class CSRF < Exception; end @@ -138,18 +141,20 @@ module Discourse return base_url_no_prefix + base_uri end - def self.enable_maintenance_mode - $redis.set maintenance_mode_key, 1 + def self.enable_readonly_mode + $redis.set readonly_mode_key, 1 + MessageBus.publish(readonly_channel, true) true end - def self.disable_maintenance_mode - $redis.del maintenance_mode_key + def self.disable_readonly_mode + $redis.del readonly_mode_key + MessageBus.publish(readonly_channel, false) true end - def self.maintenance_mode? - !!$redis.get( maintenance_mode_key ) + def self.readonly_mode? + !!$redis.get(readonly_mode_key) end def self.git_version @@ -198,9 +203,12 @@ module Discourse Rails.configuration.action_controller.asset_host end -private - - def self.maintenance_mode_key - 'maintenance_mode' + def self.readonly_mode_key + "readonly_mode" end + + def self.readonly_channel + "/global/read-only" + end + end diff --git a/spec/components/discourse_spec.rb b/spec/components/discourse_spec.rb index 66a128ead74..402f4535c05 100644 --- a/spec/components/discourse_spec.rb +++ b/spec/components/discourse_spec.rb @@ -82,5 +82,39 @@ describe Discourse do end + context "#enable_readonly_mode" do + + it "adds a key in redis and publish a message through the message bus" do + $redis.expects(:set).with(Discourse.readonly_mode_key, 1) + MessageBus.expects(:publish).with(Discourse.readonly_channel, true) + Discourse.enable_readonly_mode + end + + end + + context "#disable_readonly_mode" do + + it "removes a key from redis and publish a message through the message bus" do + $redis.expects(:del).with(Discourse.readonly_mode_key) + MessageBus.expects(:publish).with(Discourse.readonly_channel, false) + Discourse.disable_readonly_mode + end + + end + + context "#readonly_mode?" do + + it "returns true when the key is present in redis" do + $redis.expects(:get).with(Discourse.readonly_mode_key).returns("1") + Discourse.readonly_mode?.should == true + end + + it "returns false when the key is not present in redis" do + $redis.expects(:get).with(Discourse.readonly_mode_key).returns(nil) + Discourse.readonly_mode?.should == false + end + + end + end