FIX: When user has already hit bookmark limit, do not error for clear_reminder! or other updates (#12658)

We introduced a cap on the number of bookmarks the user can add in be145ccf2f. However this has caused unintended side effects; when the `jobs/scheduled/bookmark_reminder_notifications.rb` runs we get this error for users who already had more bookmarks than the limit:

> Job exception: Validation failed: Sorry, you have too many bookmarks, visit #{url}/my/activity/bookmarks to remove some.

This is because the `clear_reminder!` call was triggering a bookmark validation, which raised an error because the user already had to many, holding up other reminders.

This PR also adds `max_bookmarks_per_user` hidden site setting (default 2000). This replaces the BOOKMARK_LIMIT const so we can raise it for certain sites.
This commit is contained in:
Martin Brennan
2021-04-09 13:06:35 +10:00
committed by GitHub
parent 8339b8f412
commit 1ba5ccd8af
6 changed files with 45 additions and 14 deletions

View File

@@ -1,8 +1,6 @@
# 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
] ]
@@ -69,8 +67,17 @@ class Bookmark < ActiveRecord::Base
end end
def bookmark_limit_not_reached def bookmark_limit_not_reached
return if user.bookmarks.count < BOOKMARK_LIMIT return if user.bookmarks.count < SiteSetting.max_bookmarks_per_user
self.errors.add(:base, I18n.t("bookmarks.errors.too_many", user_bookmarks_url: "#{Discourse.base_url}/my/activity/bookmarks")) return if !new_record?
self.errors.add(
:base,
I18n.t(
"bookmarks.errors.too_many",
user_bookmarks_url: "#{Discourse.base_url}/my/activity/bookmarks",
limit: SiteSetting.max_bookmarks_per_user
)
)
end end
def no_reminder? def no_reminder?

View File

@@ -416,7 +416,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." too_many: "Sorry, you cannot add more than %{limit} bookmarks, visit <a href='%{user_bookmarks_url}'>%{user_bookmarks_url}</a> 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"

View File

@@ -659,6 +659,9 @@ users:
gravatar_login_url: gravatar_login_url:
default: /emails default: /emails
client: true client: true
max_bookmarks_per_user:
default: 2000
hidden: true
groups: groups:
enable_group_directory: enable_group_directory:

View File

@@ -48,6 +48,15 @@ RSpec.describe Jobs::BookmarkReminderNotifications do
expect(bookmark4.reload.reminder_at).not_to eq(nil) expect(bookmark4.reload.reminder_at).not_to eq(nil)
end end
context "when a user is over the bookmark limit" do
it "clearing their reminder does not error and hold up the rest" do
other_bookmark = Fabricate(:bookmark, user: bookmark1.user)
other_bookmark.update_column(:reminder_at, five_minutes_ago)
SiteSetting.max_bookmarks_per_user = 2
expect { subject.execute }.not_to raise_error
end
end
context "when the number of notifications exceed max_reminder_notifications_per_run" do context "when the number of notifications exceed max_reminder_notifications_per_run" do
it "does not send them in the current run, but will send them in the next" do it "does not send them in the current run, but will send them in the next" do
begin begin

View File

@@ -39,5 +39,24 @@ describe Bookmark do
expect(Bookmark.find(bookmark.id)).to eq(bookmark) expect(Bookmark.find(bookmark.id)).to eq(bookmark)
expect(Bookmark.find_by(id: bookmark2.id)).to eq(bookmark2) expect(Bookmark.find_by(id: bookmark2.id)).to eq(bookmark2)
end end
describe "bookmark limits" do
fab!(:user) { Fabricate(:user) }
it "does not get the bookmark limit error because it is not creating a new bookmark (for users already over the limit)" do
Fabricate(:bookmark, user: user)
Fabricate(:bookmark, user: user)
last_bookmark = Fabricate(:bookmark, user: user)
SiteSetting.max_bookmarks_per_user = 2
expect { last_bookmark.clear_reminder! }.not_to raise_error
end
it "gets the bookmark limit error when creating a new bookmark over the limit" do
Fabricate(:bookmark, user: user)
Fabricate(:bookmark, user: user)
SiteSetting.max_bookmarks_per_user = 2
expect { Fabricate(:bookmark, user: user) }.to raise_error(ActiveRecord::RecordInvalid)
end
end
end end
end end

View File

@@ -33,9 +33,7 @@ describe BookmarksController do
context "if the user reached the max bookmark limit" do context "if the user reached the max bookmark limit" do
before do before do
@old_constant = Bookmark::BOOKMARK_LIMIT SiteSetting.max_bookmarks_per_user = 1
Bookmark.send(:remove_const, "BOOKMARK_LIMIT")
Bookmark.const_set("BOOKMARK_LIMIT", 1)
end end
it "returns failed JSON with a 400 error" do it "returns failed JSON with a 400 error" do
@@ -51,14 +49,9 @@ describe BookmarksController do
expect(response.status).to eq(400) expect(response.status).to eq(400)
user_bookmarks_url = "#{Discourse.base_url}/my/activity/bookmarks" user_bookmarks_url = "#{Discourse.base_url}/my/activity/bookmarks"
expect(response.parsed_body['errors']).to include( expect(response.parsed_body['errors']).to include(
I18n.t("bookmarks.errors.too_many", user_bookmarks_url: user_bookmarks_url) I18n.t("bookmarks.errors.too_many", user_bookmarks_url: user_bookmarks_url, limit: SiteSetting.max_bookmarks_per_user)
) )
end end
after do
Bookmark.send(:remove_const, "BOOKMARK_LIMIT")
Bookmark.const_set("BOOKMARK_LIMIT", @old_constant)
end
end end
context "if the user already has bookmarked the post" do context "if the user already has bookmarked the post" do