diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb index 8a09c8fbe..99846d6dd 100644 --- a/app/controllers/application_controller.rb +++ b/app/controllers/application_controller.rb @@ -75,9 +75,13 @@ class ApplicationController < ActionController::Base render 'default/empty' end + def render_rate_limit_error(e) + render_json_error e.description, type: :rate_limit, status: 429 + end + # If they hit the rate limiter rescue_from RateLimiter::LimitExceeded do |e| - render_json_error e.description, type: :rate_limit, status: 429 + render_rate_limit_error(e) end rescue_from PG::ReadOnlySqlTransaction do |e| diff --git a/app/controllers/post_actions_controller.rb b/app/controllers/post_actions_controller.rb index 4a7585890..f707c57e6 100644 --- a/app/controllers/post_actions_controller.rb +++ b/app/controllers/post_actions_controller.rb @@ -23,6 +23,15 @@ class PostActionsController < ApplicationController @post.reload render_post_json(@post, _add_raw = false) end + rescue RateLimiter::LimitExceeded => e + # Special case: if we hit the create like rate limit, record it in user history + # so we can award a badge + if e.type == "create_like" + UserHistory.create!(action: UserHistory.actions[:rate_limited_like], + target_user_id: current_user.id, + post_id: @post.id) + end + render_rate_limit_error(e) end def destroy diff --git a/app/models/badge.rb b/app/models/badge.rb index dd01145ef..467a72157 100644 --- a/app/models/badge.rb +++ b/app/models/badge.rb @@ -28,6 +28,7 @@ class Badge < ActiveRecord::Base FamousLink = 30 Admired = 31 GivesBack = 32 + Generous = 33 # other consts AutobiographerMinBioLength = 10 @@ -218,6 +219,14 @@ SQL HAVING us.likes_given::float / count(*) > 5.0 SQL + Generous = <<-SQL + SELECT uh.target_user_id AS user_id, MIN(uh.created_at) AS granted_at + FROM user_histories AS uh + WHERE uh.action = #{UserHistory.actions[:rate_limited_like]} + AND (:backfill OR uh.target_user_id IN (:user_ids)) + GROUP BY uh.target_user_id +SQL + def self.invite_badge(count,trust_level) " SELECT u.id user_id, current_timestamp granted_at diff --git a/app/models/user_history.rb b/app/models/user_history.rb index e142d9f5a..ccabc8ce6 100644 --- a/app/models/user_history.rb +++ b/app/models/user_history.rb @@ -51,7 +51,8 @@ class UserHistory < ActiveRecord::Base revoke_admin: 33, grant_moderation: 34, revoke_moderation: 35, - backup_operation: 36) + backup_operation: 36, + rate_limited_like: 37) end # Staff actions is a subset of all actions, used to audit actions taken by staff users. diff --git a/config/locales/client.en.yml b/config/locales/client.en.yml index 736e318b2..ac91569ce 100644 --- a/config/locales/client.en.yml +++ b/config/locales/client.en.yml @@ -2970,6 +2970,9 @@ en: gives_back: name: Gives Back description: Has a high ratio of likes given to likes received + generous: + name: Generous + description: Used the maximum amount of likes in a day google_search: |

Search with Google

diff --git a/db/fixtures/006_badges.rb b/db/fixtures/006_badges.rb index c4cf23c18..bfb86cf86 100644 --- a/db/fixtures/006_badges.rb +++ b/db/fixtures/006_badges.rb @@ -316,6 +316,18 @@ Badge.seed do |b| b.system = true end +Badge.seed do |b| + b.id = Badge::Generous + b.default_name = "Generous" + b.default_icon = "fa-heart" + b.badge_type_id = BadgeType::Silver + b.query = Badge::Queries::Generous + b.default_badge_grouping_id = BadgeGrouping::Community + b.trigger = Badge::Trigger::None + b.auto_revoke = false + b.system = true +end + Badge.where("NOT system AND id < 100").each do |badge| new_id = [Badge.maximum(:id) + 1, 100].max old_id = badge.id diff --git a/lib/rate_limiter/limit_exceeded.rb b/lib/rate_limiter/limit_exceeded.rb index b4a49e291..787bcdd38 100644 --- a/lib/rate_limiter/limit_exceeded.rb +++ b/lib/rate_limiter/limit_exceeded.rb @@ -2,6 +2,7 @@ class RateLimiter # A rate limit has been exceeded. class LimitExceeded < StandardError + attr_reader :type def initialize(available_in, type=nil) @available_in = available_in diff --git a/spec/components/rate_limiter_spec.rb b/spec/components/rate_limiter_spec.rb index 1298523e0..e997db7c6 100644 --- a/spec/components/rate_limiter_spec.rb +++ b/spec/components/rate_limiter_spec.rb @@ -94,10 +94,6 @@ describe RateLimiter do end end - end - - - end diff --git a/spec/controllers/post_actions_controller_spec.rb b/spec/controllers/post_actions_controller_spec.rb index 5cbad63a8..4a828893a 100644 --- a/spec/controllers/post_actions_controller_spec.rb +++ b/spec/controllers/post_actions_controller_spec.rb @@ -7,7 +7,22 @@ describe PostActionsController do expect { xhr :post, :create }.to raise_error(Discourse::NotLoggedIn) end - describe 'logged in' do + describe 'logged in as regular user' do + before do + @user = log_in(:user) + @post = Fabricate(:post, user: Fabricate(:coding_horror)) + end + + it 'creates user history if the rate limit for a like is hit' do + PostAction.expects(:act).once.raises(RateLimiter::LimitExceeded.new(60, 'create_like')) + expect(-> { + xhr :post, :create, id: @post.id, post_action_type_id: PostActionType.types[:like] + puts response.success? + }).to change(UserHistory, :count).by(1) + end + end + + describe 'logged in as moderator' do before do @user = log_in(:moderator) @post = Fabricate(:post, user: Fabricate(:coding_horror))