FIX: Add unique index on group_requests(group_id, user_id). (#7399)

FIX: Add unique index on group_requests(group_id, user_id). (#7399)

diff --git a/app/controllers/groups_controller.rb b/app/controllers/groups_controller.rb
index df5e1e0..9fc82d1 100644
--- a/app/controllers/groups_controller.rb
+++ b/app/controllers/groups_controller.rb
@@ -408,12 +408,13 @@ class GroupsController < ApplicationController
   def request_membership
     params.require(:reason)
 
-    unless current_user.staff?
-      RateLimiter.new(current_user, "request_group_membership", 1, 1.day).performed!
-    end
-
     group = find_group(:id)
-    group_name = group.name
+
+    begin
+      GroupRequest.create!(group: group, user: current_user, reason: params[:reason])
+    rescue ActiveRecord::RecordNotUnique => e
+      return render json: failed_json.merge(error: I18n.t("groups.errors.already_requested_membership")), status: 409
+    end
 
     usernames = [current_user.username].concat(
       group.users.where('group_users.owner')
@@ -432,15 +433,13 @@ class GroupsController < ApplicationController
     EOF
 
     post = PostCreator.new(current_user,
-      title: I18n.t('groups.request_membership_pm.title', group_name: group_name),
+      title: I18n.t('groups.request_membership_pm.title', group_name: group.name),
       raw: raw,
       archetype: Archetype.private_message,
       target_usernames: usernames.join(','),
       skip_validations: true
     ).create!
 
-    GroupRequest.create!(group: group, user: current_user, reason: params[:reason])
-
     render json: success_json.merge(relative_url: post.topic.relative_url)
   end
 
diff --git a/config/locales/server.en.yml b/config/locales/server.en.yml
index 8373056..ab44a7e 100644
--- a/config/locales/server.en.yml
+++ b/config/locales/server.en.yml
@@ -364,6 +364,7 @@ en:
       email_already_used_in_group: "'%{email}' is already used by the group '%{group_name}'."
       email_already_used_in_category: "'%{email}' is already used by the category '%{category_name}'."
       cant_allow_membership_requests: "You cannot allow membership requests for a group without any owners."
+      already_requested_membership: "You have already requested membership for this group."
     default_names:
       everyone: "everyone"
       admins: "admins"
diff --git a/db/migrate/20190418113814_add_unique_index_to_group_requests.rb b/db/migrate/20190418113814_add_unique_index_to_group_requests.rb
new file mode 100644
index 0000000..194777d
--- /dev/null
+++ b/db/migrate/20190418113814_add_unique_index_to_group_requests.rb
@@ -0,0 +1,6 @@
+class AddUniqueIndexToGroupRequests < ActiveRecord::Migration[5.2]
+  def change
+    execute "DELETE FROM group_requests WHERE id NOT IN (SELECT MIN(id) FROM group_requests GROUP BY group_id, user_id)"
+    add_index :group_requests, [:group_id, :user_id], unique: true
+  end
+end
diff --git a/spec/requests/groups_controller_spec.rb b/spec/requests/groups_controller_spec.rb
index 51cf586..f4cac12 100644
--- a/spec/requests/groups_controller_spec.rb
+++ b/spec/requests/groups_controller_spec.rb
@@ -1275,6 +1275,20 @@ describe GroupsController do
       expect(response.status).to eq(400)
     end
 
+    it 'checks for duplicates' do
+      sign_in(user)
+
+      post "/groups/#{group.name}/request_membership.json",
+        params: { reason: 'Please add me in' }
+
+      expect(response.status).to eq(200)
+
+      post "/groups/#{group.name}/request_membership.json",
+        params: { reason: 'Please add me in' }
+
+      expect(response.status).to eq(409)
+    end
+
     it 'should create the right PM' do
       owner1 = Fabricate(:user, last_seen_at: Time.zone.now)
       owner2 = Fabricate(:user, last_seen_at: Time.zone.now - 1 .day)

GitHub sha: 9050b1bf

1 Like