FIX: add mutex around un/mark as solved

To prevent any race conditions when two users toggle the state of a reply.

(cf. https://meta.discourse.org/161104)
This commit is contained in:
Régis Hanol 2020-08-17 16:33:24 +02:00
parent e16784fd07
commit 9ecac7a3e2

View File

@ -77,6 +77,8 @@ SQL
def self.accept_answer!(post, acting_user, topic: nil) def self.accept_answer!(post, acting_user, topic: nil)
topic ||= post.topic topic ||= post.topic
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
accepted_id = topic.custom_fields["accepted_answer_post_id"].to_i accepted_id = topic.custom_fields["accepted_answer_post_id"].to_i
if accepted_id > 0 if accepted_id > 0
@ -157,9 +159,12 @@ SQL
DiscourseEvent.trigger(:accepted_solution, post) DiscourseEvent.trigger(:accepted_solution, post)
end end
end
def self.unaccept_answer!(post, topic: nil) def self.unaccept_answer!(post, topic: nil)
topic ||= post.topic topic ||= post.topic
DistributedMutex.synchronize("discourse_solved_toggle_answer_#{topic.id}") do
post.custom_fields["is_accepted_answer"] = nil post.custom_fields["is_accepted_answer"] = nil
topic.custom_fields["accepted_answer_post_id"] = nil topic.custom_fields["accepted_answer_post_id"] = nil
@ -198,12 +203,13 @@ SQL
DiscourseEvent.trigger(:unaccepted_solution, post) DiscourseEvent.trigger(:unaccepted_solution, post)
end end
end end
end
require_dependency "application_controller" require_dependency "application_controller"
class DiscourseSolved::AnswerController < ::ApplicationController class DiscourseSolved::AnswerController < ::ApplicationController
def accept def accept
limit_accepts limit_accepts
post = Post.find(params[:id].to_i) post = Post.find(params[:id].to_i)
@ -219,7 +225,6 @@ SQL
end end
def unaccept def unaccept
limit_accepts limit_accepts
post = Post.find(params[:id].to_i) post = Post.find(params[:id].to_i)
@ -234,12 +239,11 @@ SQL
end end
def limit_accepts def limit_accepts
unless current_user.staff? return if current_user.staff?
RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed! RateLimiter.new(nil, "accept-hr-#{current_user.id}", 20, 1.hour).performed!
RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed! RateLimiter.new(nil, "accept-min-#{current_user.id}", 4, 30.seconds).performed!
end end
end end
end
DiscourseSolved::Engine.routes.draw do DiscourseSolved::Engine.routes.draw do
post "/accept" => "answer#accept" post "/accept" => "answer#accept"
@ -477,7 +481,6 @@ SQL
end end
topic.user_id == current_user.id && !topic.closed topic.user_id == current_user.id && !topic.closed
end end
end end
@ -486,9 +489,7 @@ SQL
attributes :can_accept_answer, :can_unaccept_answer, :accepted_answer attributes :can_accept_answer, :can_unaccept_answer, :accepted_answer
def can_accept_answer def can_accept_answer
topic = (topic_view && topic_view.topic) || object.topic if topic = (topic_view && topic_view.topic) || object.topic
if topic
return scope.can_accept_answer?(topic, object) && object.post_number > 1 && !accepted_answer return scope.can_accept_answer?(topic, object) && object.post_number > 1 && !accepted_answer
end end
@ -496,8 +497,7 @@ SQL
end end
def can_unaccept_answer def can_unaccept_answer
topic = (topic_view && topic_view.topic) || object.topic if topic = (topic_view && topic_view.topic) || object.topic
if topic
scope.can_accept_answer?(topic, object) && (post_custom_fields["is_accepted_answer"] == 'true') scope.can_accept_answer?(topic, object) && (post_custom_fields["is_accepted_answer"] == 'true')
end end
end end