Extract reaction create from controller to service (#18350)
* Extract reaction create from controller to service * Success is having no errors * Confirm http status successful
This commit is contained in:
parent
a4ffb7cac3
commit
31296499da
3 changed files with 189 additions and 108 deletions
|
|
@ -55,55 +55,13 @@ class ReactionsController < ApplicationController
|
|||
def create
|
||||
remove_count_cache_key
|
||||
|
||||
if params[:reactable_type] == "Article" && params[:category].in?(Reaction::PRIVILEGED_CATEGORIES)
|
||||
clear_moderator_reactions(
|
||||
params[:reactable_id],
|
||||
params[:reactable_type],
|
||||
current_user,
|
||||
params[:category],
|
||||
)
|
||||
end
|
||||
result = ReactionToggle.toggle(params, current_user: current_user)
|
||||
|
||||
category = params[:category] || "like"
|
||||
|
||||
reaction = Reaction.where(
|
||||
user_id: current_user.id,
|
||||
reactable_id: params[:reactable_id],
|
||||
reactable_type: params[:reactable_type],
|
||||
category: category,
|
||||
).first
|
||||
|
||||
# if the reaction already exists, destroy it
|
||||
if reaction
|
||||
result = handle_existing_reaction(reaction)
|
||||
if result.success?
|
||||
render json: { result: result.action, category: result.category }
|
||||
else
|
||||
reaction = build_reaction(category)
|
||||
|
||||
if reaction.save
|
||||
rate_limiter.track_limit_by_action(:reaction_creation)
|
||||
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
||||
|
||||
Notification.send_reaction_notification(reaction, reaction.target_user)
|
||||
if reaction.reaction_on_organization_article?
|
||||
Notification.send_reaction_notification(reaction,
|
||||
reaction.reactable.organization)
|
||||
end
|
||||
|
||||
result = "create"
|
||||
|
||||
if category == "readinglist" && current_user.setting.experience_level
|
||||
rate_article(reaction)
|
||||
end
|
||||
|
||||
if current_user.auditable?
|
||||
Audit::Logger.log(:moderator, current_user, params.dup)
|
||||
end
|
||||
else
|
||||
render json: { error: reaction.errors_as_sentence, status: 422 }, status: :unprocessable_entity
|
||||
return
|
||||
end
|
||||
render json: { error: result.errors_as_sentence, status: 422 }, status: :unprocessable_entity
|
||||
end
|
||||
render json: { result: result, category: category }
|
||||
end
|
||||
|
||||
def cached_user_public_comment_reactions(user, comment_ids)
|
||||
|
|
@ -118,67 +76,6 @@ class ReactionsController < ApplicationController
|
|||
|
||||
private
|
||||
|
||||
def build_reaction(category)
|
||||
create_params = {
|
||||
user_id: current_user.id,
|
||||
reactable_id: params[:reactable_id],
|
||||
reactable_type: params[:reactable_type],
|
||||
category: category
|
||||
}
|
||||
if (current_user&.any_admin? || current_user&.super_moderator?) &&
|
||||
Reaction::NEGATIVE_PRIVILEGED_CATEGORIES.include?(category)
|
||||
create_params[:status] = "confirmed"
|
||||
end
|
||||
Reaction.new(create_params)
|
||||
end
|
||||
|
||||
def destroy_reaction(reaction)
|
||||
reaction.destroy
|
||||
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
||||
Notification.send_reaction_notification_without_delay(reaction, reaction.target_user)
|
||||
if reaction.reaction_on_organization_article?
|
||||
Notification.send_reaction_notification_without_delay(reaction,
|
||||
reaction.reactable.organization)
|
||||
end
|
||||
"destroy"
|
||||
end
|
||||
|
||||
def rate_article(reaction)
|
||||
user_experience_level = current_user.setting.experience_level
|
||||
return unless user_experience_level
|
||||
|
||||
RatingVote.create(article_id: reaction.reactable_id,
|
||||
group: "experience_level",
|
||||
user_id: current_user.id,
|
||||
context: "readinglist_reaction",
|
||||
rating: user_experience_level)
|
||||
end
|
||||
|
||||
def clear_moderator_reactions(id, type, mod, category)
|
||||
reactions = if category == "thumbsup"
|
||||
Reaction.where(reactable_id: id, reactable_type: type, user: mod,
|
||||
category: Reaction::NEGATIVE_PRIVILEGED_CATEGORIES)
|
||||
elsif category.in?(Reaction::NEGATIVE_PRIVILEGED_CATEGORIES)
|
||||
Reaction.where(reactable_id: id, reactable_type: type, user: mod, category: "thumbsup")
|
||||
end
|
||||
|
||||
return if reactions.blank?
|
||||
|
||||
reactions.find_each { |reaction| destroy_reaction(reaction) }
|
||||
end
|
||||
|
||||
def handle_existing_reaction(reaction)
|
||||
result = destroy_reaction(reaction)
|
||||
|
||||
if reaction.negative? && current_user.auditable?
|
||||
updated_params = params.dup
|
||||
updated_params[:action] = "destroy"
|
||||
Audit::Logger.log(:moderator, current_user, updated_params)
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
def check_limit
|
||||
rate_limit!(:reaction_creation)
|
||||
end
|
||||
|
|
@ -190,6 +87,7 @@ class ReactionsController < ApplicationController
|
|||
authorize(Reaction, policy_query)
|
||||
end
|
||||
|
||||
# TODO: should this move to toggle service? refactor?
|
||||
def remove_count_cache_key
|
||||
return unless params[:reactable_type] == "Article"
|
||||
|
||||
|
|
|
|||
141
app/services/reaction_toggle.rb
Normal file
141
app/services/reaction_toggle.rb
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
class ReactionToggle
|
||||
class Result
|
||||
attr_accessor :action, :category, :reaction
|
||||
|
||||
delegate :errors_as_sentence, to: :reaction
|
||||
|
||||
def initialize(reaction: nil, action: nil, category: nil)
|
||||
@reaction = reaction
|
||||
@category = category
|
||||
@action = action
|
||||
end
|
||||
|
||||
def success?
|
||||
reaction.errors.none?
|
||||
end
|
||||
end
|
||||
|
||||
def self.toggle(params, current_user:)
|
||||
new(params, current_user: current_user).toggle
|
||||
end
|
||||
|
||||
def initialize(params, current_user:)
|
||||
@params = params
|
||||
@current_user = current_user
|
||||
@category = params[:category] || "like"
|
||||
end
|
||||
|
||||
attr_reader :category, :params, :current_user
|
||||
|
||||
delegate :rate_limiter, to: :current_user
|
||||
|
||||
def toggle
|
||||
if params[:reactable_type] == "Article" && params[:category].in?(Reaction::PRIVILEGED_CATEGORIES)
|
||||
clear_moderator_reactions(
|
||||
params[:reactable_id],
|
||||
params[:reactable_type],
|
||||
current_user,
|
||||
params[:category],
|
||||
)
|
||||
end
|
||||
|
||||
reaction = Reaction.where(
|
||||
user_id: current_user.id,
|
||||
reactable_id: params[:reactable_id],
|
||||
reactable_type: params[:reactable_type],
|
||||
category: category,
|
||||
).first
|
||||
|
||||
# if the reaction already exists, destroy it
|
||||
return handle_existing_reaction(reaction) if reaction
|
||||
|
||||
reaction = build_reaction(category)
|
||||
result = Result.new reaction: reaction, category: category
|
||||
|
||||
if reaction.save
|
||||
rate_limiter.track_limit_by_action(:reaction_creation)
|
||||
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
||||
|
||||
Notification.send_reaction_notification(reaction, reaction.target_user)
|
||||
if reaction.reaction_on_organization_article?
|
||||
Notification.send_reaction_notification(reaction,
|
||||
reaction.reactable.organization)
|
||||
end
|
||||
|
||||
result.action = "create"
|
||||
|
||||
if category == "readinglist" && current_user.setting.experience_level
|
||||
rate_article(reaction)
|
||||
end
|
||||
|
||||
if current_user.auditable?
|
||||
Audit::Logger.log(:moderator, current_user, params.dup)
|
||||
end
|
||||
end
|
||||
|
||||
result
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def clear_moderator_reactions(id, type, mod, category)
|
||||
reactions = if category == "thumbsup"
|
||||
Reaction.where(reactable_id: id, reactable_type: type, user: mod,
|
||||
category: Reaction::NEGATIVE_PRIVILEGED_CATEGORIES)
|
||||
elsif category.in?(Reaction::NEGATIVE_PRIVILEGED_CATEGORIES)
|
||||
Reaction.where(reactable_id: id, reactable_type: type, user: mod, category: "thumbsup")
|
||||
end
|
||||
|
||||
return if reactions.blank?
|
||||
|
||||
reactions.find_each { |reaction| destroy_reaction(reaction) }
|
||||
end
|
||||
|
||||
def handle_existing_reaction(reaction)
|
||||
destroy_reaction(reaction)
|
||||
|
||||
if reaction.negative? && current_user.auditable?
|
||||
updated_params = params.dup
|
||||
updated_params[:action] = "destroy"
|
||||
Audit::Logger.log(:moderator, current_user, updated_params)
|
||||
end
|
||||
|
||||
Result.new category: category, reaction: reaction, action: "destroy"
|
||||
end
|
||||
|
||||
def build_reaction(category)
|
||||
create_params = {
|
||||
user_id: current_user.id,
|
||||
reactable_id: params[:reactable_id],
|
||||
reactable_type: params[:reactable_type],
|
||||
category: category
|
||||
}
|
||||
if (current_user&.any_admin? || current_user&.super_moderator?) &&
|
||||
Reaction::NEGATIVE_PRIVILEGED_CATEGORIES.include?(category)
|
||||
create_params[:status] = "confirmed"
|
||||
end
|
||||
Reaction.new(create_params)
|
||||
end
|
||||
|
||||
def destroy_reaction(reaction)
|
||||
reaction.destroy
|
||||
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
||||
Notification.send_reaction_notification_without_delay(reaction, reaction.target_user)
|
||||
if reaction.reaction_on_organization_article?
|
||||
Notification.send_reaction_notification_without_delay(reaction,
|
||||
reaction.reactable.organization)
|
||||
end
|
||||
"destroy"
|
||||
end
|
||||
|
||||
def rate_article(reaction)
|
||||
user_experience_level = current_user.setting.experience_level
|
||||
return unless user_experience_level
|
||||
|
||||
RatingVote.create(article_id: reaction.reactable_id,
|
||||
group: "experience_level",
|
||||
user_id: current_user.id,
|
||||
context: "readinglist_reaction",
|
||||
rating: user_experience_level)
|
||||
end
|
||||
end
|
||||
|
|
@ -183,7 +183,7 @@ RSpec.describe "Reactions", type: :request do
|
|||
allow(rate_limiter).to receive(:limit_by_action).and_return(true)
|
||||
post "/reactions", params: article_params
|
||||
|
||||
expect(response.status).to eq(429)
|
||||
expect(response).to have_http_status(:too_many_requests)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
@ -205,6 +205,11 @@ RSpec.describe "Reactions", type: :request do
|
|||
post "/reactions", params: article_params
|
||||
end.to change(Reaction, :count).by(-1)
|
||||
end
|
||||
|
||||
it "has success http status" do
|
||||
post "/reactions", params: article_params
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context "when creating readinglist" do
|
||||
|
|
@ -247,6 +252,15 @@ RSpec.describe "Reactions", type: :request do
|
|||
|
||||
expect(RatingVote).not_to have_received(:create)
|
||||
end
|
||||
|
||||
it "has success http status" do
|
||||
post "/reactions", params: {
|
||||
reactable_id: article.id,
|
||||
reactable_type: "Article",
|
||||
category: "readinglist"
|
||||
}
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context "when attempting to create thumbsup as regular user" do
|
||||
|
|
@ -284,6 +298,15 @@ RSpec.describe "Reactions", type: :request do
|
|||
expect(Reaction.where(category: "thumbsdown").count).to eq(0)
|
||||
expect(Reaction.where(category: "like").count).to eq(1)
|
||||
end
|
||||
|
||||
it "has success http status" do
|
||||
post "/reactions", params: {
|
||||
reactable_id: article.id,
|
||||
reactable_type: "Article",
|
||||
category: "thumbsup"
|
||||
}
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context "when creating thumbsdown" do
|
||||
|
|
@ -306,6 +329,15 @@ RSpec.describe "Reactions", type: :request do
|
|||
expect(Reaction.where(category: "like").size).to be 1
|
||||
expect(Reaction.where(category: "vomit").size).to be 1
|
||||
end
|
||||
|
||||
it "has success http status" do
|
||||
post "/reactions", params: {
|
||||
reactable_id: article.id,
|
||||
reactable_type: "Article",
|
||||
category: "thumbsdown"
|
||||
}
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context "when vomiting on a user" do
|
||||
|
|
@ -325,6 +357,11 @@ RSpec.describe "Reactions", type: :request do
|
|||
post "/reactions", params: user_params
|
||||
end.to change(Reaction, :count).by(-1)
|
||||
end
|
||||
|
||||
it "has success http status" do
|
||||
post "/reactions", params: user_params
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context "when signed in as admin" do
|
||||
|
|
@ -357,6 +394,11 @@ RSpec.describe "Reactions", type: :request do
|
|||
expect(reaction.category).to eq("like")
|
||||
expect(reaction.status).to eq("valid")
|
||||
end
|
||||
|
||||
it "has success http status" do
|
||||
post "/reactions", params: article_params
|
||||
expect(response).to be_successful
|
||||
end
|
||||
end
|
||||
|
||||
context "when signed out" do
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue