FIX: Add bookmark limits (#11725)
Adds a bookmark search per page limit, a total bookmark creation limit, and a rate limit per day for bookmark creation.
This commit is contained in:
parent
7374eeb447
commit
be145ccf2f
|
@ -6,6 +6,10 @@ class BookmarksController < ApplicationController
|
||||||
def create
|
def create
|
||||||
params.require(:post_id)
|
params.require(:post_id)
|
||||||
|
|
||||||
|
RateLimiter.new(
|
||||||
|
current_user, "create_bookmark", SiteSetting.max_bookmarks_per_day, 1.day.to_i
|
||||||
|
).performed!
|
||||||
|
|
||||||
bookmark_manager = BookmarkManager.new(current_user)
|
bookmark_manager = BookmarkManager.new(current_user)
|
||||||
bookmark = bookmark_manager.create(
|
bookmark = bookmark_manager.create(
|
||||||
post_id: params[:post_id],
|
post_id: params[:post_id],
|
||||||
|
|
|
@ -1,6 +1,8 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Bookmark < ActiveRecord::Base
|
class Bookmark < ActiveRecord::Base
|
||||||
|
BOOKMARK_LIMIT = 2000
|
||||||
|
|
||||||
self.ignored_columns = [
|
self.ignored_columns = [
|
||||||
"delete_when_reminder_sent" # TODO(2021-07-22): remove
|
"delete_when_reminder_sent" # TODO(2021-07-22): remove
|
||||||
]
|
]
|
||||||
|
@ -37,6 +39,7 @@ class Bookmark < ActiveRecord::Base
|
||||||
|
|
||||||
validate :unique_per_post_for_user
|
validate :unique_per_post_for_user
|
||||||
validate :ensure_sane_reminder_at_time
|
validate :ensure_sane_reminder_at_time
|
||||||
|
validate :bookmark_limit_not_reached
|
||||||
validates :name, length: { maximum: 100 }
|
validates :name, length: { maximum: 100 }
|
||||||
|
|
||||||
# we don't care whether the post or topic is deleted,
|
# we don't care whether the post or topic is deleted,
|
||||||
|
@ -65,6 +68,11 @@ class Bookmark < ActiveRecord::Base
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def bookmark_limit_not_reached
|
||||||
|
return if user.bookmarks.count < BOOKMARK_LIMIT
|
||||||
|
self.errors.add(:base, I18n.t("bookmarks.errors.too_many", user_bookmarks_url: "#{Discourse.base_url}/my/activity/bookmarks"))
|
||||||
|
end
|
||||||
|
|
||||||
def no_reminder?
|
def no_reminder?
|
||||||
self.reminder_at.blank? && self.reminder_type.blank?
|
self.reminder_at.blank? && self.reminder_type.blank?
|
||||||
end
|
end
|
||||||
|
|
|
@ -5,13 +5,17 @@ class UserBookmarkList
|
||||||
|
|
||||||
PER_PAGE = 20
|
PER_PAGE = 20
|
||||||
|
|
||||||
attr_reader :bookmarks
|
attr_reader :bookmarks, :per_page
|
||||||
attr_accessor :more_bookmarks_url
|
attr_accessor :more_bookmarks_url
|
||||||
|
|
||||||
def initialize(user:, guardian:, params:)
|
def initialize(user:, guardian:, params:)
|
||||||
@user = user
|
@user = user
|
||||||
@guardian = guardian
|
@guardian = guardian
|
||||||
@params = params.merge(per_page: PER_PAGE)
|
@params = params
|
||||||
|
|
||||||
|
@params.merge!(per_page: PER_PAGE) if params[:per_page].blank?
|
||||||
|
@params[:per_page] = PER_PAGE if @params[:per_page] > PER_PAGE
|
||||||
|
|
||||||
@bookmarks = []
|
@bookmarks = []
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -21,6 +25,6 @@ class UserBookmarkList
|
||||||
end
|
end
|
||||||
|
|
||||||
def per_page
|
def per_page
|
||||||
@per_page ||= PER_PAGE
|
@per_page ||= @params[:per_page]
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -413,6 +413,7 @@ en:
|
||||||
bookmarks:
|
bookmarks:
|
||||||
errors:
|
errors:
|
||||||
already_bookmarked_post: "You cannot bookmark the same post twice."
|
already_bookmarked_post: "You cannot bookmark the same post twice."
|
||||||
|
too_many: "Sorry, you have too many bookmarks, visit %{user_bookmarks_url} to remove some."
|
||||||
cannot_set_past_reminder: "You cannot set a bookmark reminder in the past."
|
cannot_set_past_reminder: "You cannot set a bookmark reminder in the past."
|
||||||
cannot_set_reminder_in_distant_future: "You cannot set a bookmark reminder more than 10 years in the future."
|
cannot_set_reminder_in_distant_future: "You cannot set a bookmark reminder more than 10 years in the future."
|
||||||
time_must_be_provided: "time must be provided for all reminders"
|
time_must_be_provided: "time must be provided for all reminders"
|
||||||
|
|
|
@ -0,0 +1,27 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
require 'rails_helper'
|
||||||
|
|
||||||
|
RSpec.describe UserBookmarkList do
|
||||||
|
let(:params) { {} }
|
||||||
|
fab!(:user) { Fabricate(:user) }
|
||||||
|
let(:list) { UserBookmarkList.new(user: user, guardian: Guardian.new(user), params: params) }
|
||||||
|
|
||||||
|
before do
|
||||||
|
22.times do
|
||||||
|
Fabricate(:bookmark, user: user)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
it "defaults to 20 per page" do
|
||||||
|
expect(list.per_page).to eq(20)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "when the per_page param is too high" do
|
||||||
|
let(:params) { { per_page: 1000 } }
|
||||||
|
|
||||||
|
it "does not allow more than X bookmarks to be requested per page" do
|
||||||
|
expect(list.load.count).to eq(20)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -12,6 +12,58 @@ describe BookmarksController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe "#create" do
|
describe "#create" do
|
||||||
|
it "rate limits creates" do
|
||||||
|
SiteSetting.max_bookmarks_per_day = 1
|
||||||
|
RateLimiter.enable
|
||||||
|
RateLimiter.clear_all!
|
||||||
|
|
||||||
|
post "/bookmarks.json", params: {
|
||||||
|
post_id: bookmark_post.id,
|
||||||
|
reminder_type: "tomorrow",
|
||||||
|
reminder_at: (Time.zone.now + 1.day).iso8601
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(200)
|
||||||
|
|
||||||
|
post "/bookmarks.json", params: {
|
||||||
|
post_id: Fabricate(:post).id
|
||||||
|
}
|
||||||
|
expect(response.status).to eq(429)
|
||||||
|
expect(response.parsed_body['errors']).to include(
|
||||||
|
I18n.t("rate_limiter.by_type.create_bookmark", time_left: "24 hours")
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
context "if the user reached the max bookmark limit" do
|
||||||
|
before do
|
||||||
|
@old_constant = Bookmark::BOOKMARK_LIMIT
|
||||||
|
Bookmark.send(:remove_const, "BOOKMARK_LIMIT")
|
||||||
|
Bookmark.const_set("BOOKMARK_LIMIT", 1)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "returns failed JSON with a 400 error" do
|
||||||
|
post "/bookmarks.json", params: {
|
||||||
|
post_id: bookmark_post.id,
|
||||||
|
reminder_type: "tomorrow",
|
||||||
|
reminder_at: (Time.zone.now + 1.day).iso8601
|
||||||
|
}
|
||||||
|
post "/bookmarks.json", params: {
|
||||||
|
post_id: Fabricate(:post).id
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(response.status).to eq(400)
|
||||||
|
user_bookmarks_url = "#{Discourse.base_url}/my/activity/bookmarks"
|
||||||
|
expect(response.parsed_body['errors']).to include(
|
||||||
|
I18n.t("bookmarks.errors.too_many", user_bookmarks_url: user_bookmarks_url)
|
||||||
|
)
|
||||||
|
end
|
||||||
|
|
||||||
|
after do
|
||||||
|
Bookmark.send(:remove_const, "BOOKMARK_LIMIT")
|
||||||
|
Bookmark.const_set("BOOKMARK_LIMIT", @old_constant)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
context "if the user already has bookmarked the post" do
|
context "if the user already has bookmarked the post" do
|
||||||
before do
|
before do
|
||||||
Fabricate(:bookmark, post: bookmark_post, user: bookmark_user)
|
Fabricate(:bookmark, post: bookmark_post, user: bookmark_user)
|
||||||
|
|
Loading…
Reference in New Issue