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:
Joshua Wehner 2022-08-23 10:09:47 +02:00 committed by GitHub
parent a4ffb7cac3
commit 31296499da
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 189 additions and 108 deletions

View file

@ -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"

View 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

View file

@ -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