* Add audit logging for negative reactions This change saves requests that create negative reactions to the audit log. We need to be monitoring admin and moderator actions throughout the app to increase visiblity on how our tools are being used and identify abuse. Additionally, this will help east concerns about giving elevated users more powerful tools. * Add a basic UI for moderator audit logs * Log experience level ratings * Log tag adjustments * UI tweaks for moderator actions log * Add logging to internal reactions controller * Add logging for admin actions on users and articles * Add searching to the moderator actions table * Move audit instrumentation out of concern We are able to use a PORO here instead of using a Rails concern. Moving this also makes syntax to audit something quite a bit more explicit and obvious to future developers * Change moderator logs pagination page length * Move auditing to after action filters * Add moderator actions to the internal navbar * Add request spec for internal moderator actions * Use request params to populate AuditLog slug
133 lines
4.3 KiB
Ruby
133 lines
4.3 KiB
Ruby
class ReactionsController < ApplicationController
|
|
before_action :set_cache_control_headers, only: [:index], unless: -> { current_user }
|
|
after_action :verify_authorized
|
|
|
|
def index
|
|
skip_authorization
|
|
|
|
if params[:article_id]
|
|
id = params[:article_id]
|
|
|
|
reactions = if session_current_user_id
|
|
Reaction.positive.
|
|
where(
|
|
reactable_id: id,
|
|
reactable_type: "Article",
|
|
user_id: session_current_user_id,
|
|
)
|
|
else
|
|
Reaction.none
|
|
end
|
|
|
|
result = { article_reaction_counts: Reaction.count_for_article(id) }
|
|
else
|
|
comments = Comment.
|
|
where(commentable_id: params[:commentable_id], commentable_type: params[:commentable_type]).
|
|
select(%i[id positive_reactions_count])
|
|
|
|
reaction_counts = comments.map do |comment|
|
|
{ id: comment.id, count: comment.positive_reactions_count }
|
|
end
|
|
|
|
reactions = if session_current_user_id
|
|
comment_ids = reaction_counts.map { |rc| rc[:id] }
|
|
cached_user_positive_reactions(current_user).where(reactable_id: comment_ids)
|
|
else
|
|
Reaction.none
|
|
end
|
|
|
|
result = { positive_reaction_counts: reaction_counts }
|
|
end
|
|
|
|
render json: {
|
|
current_user: { id: session_current_user_id },
|
|
reactions: reactions
|
|
}.merge(result).to_json
|
|
|
|
set_surrogate_key_header params.to_s unless session_current_user_id
|
|
end
|
|
|
|
def create
|
|
authorize Reaction
|
|
|
|
Rails.cache.delete "count_for_reactable-#{params[:reactable_type]}-#{params[:reactable_id]}"
|
|
|
|
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 = 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
|
|
else
|
|
reaction = build_reaction(category)
|
|
|
|
if reaction.save
|
|
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
|
|
|
Notification.send_reaction_notification(reaction, reaction.target_user)
|
|
Notification.send_reaction_notification(reaction, reaction.reactable.organization) if reaction.reaction_on_organization_article?
|
|
|
|
result = "create"
|
|
|
|
if category == "readinglist" && current_user.experience_level
|
|
rate_article(reaction)
|
|
end
|
|
|
|
if reaction.negative? && current_user.auditable?
|
|
Audit::Logger.log(:moderator, current_user, params.dup)
|
|
end
|
|
else
|
|
render json: { error: reaction.errors.full_messages.join(", "), status: 422 }, status: :unprocessable_entity
|
|
return
|
|
end
|
|
end
|
|
render json: { result: result, category: category }
|
|
end
|
|
|
|
def cached_user_positive_reactions(user)
|
|
Rails.cache.fetch("cached_user_reactions-#{user.id}-#{user.updated_at}", expires_in: 24.hours) do
|
|
user.reactions.positive
|
|
end
|
|
end
|
|
|
|
private
|
|
|
|
def build_reaction(category)
|
|
create_params = {
|
|
user_id: current_user.id,
|
|
reactable_id: params[:reactable_id],
|
|
reactable_type: params[:reactable_type],
|
|
category: category
|
|
}
|
|
create_params[:status] = "confirmed" if current_user&.any_admin?
|
|
Reaction.new(create_params)
|
|
end
|
|
|
|
def destroy_reaction(reaction)
|
|
current_user.touch
|
|
reaction.destroy
|
|
Moderator::SinkArticles.call(reaction.reactable_id) if reaction.vomit_on_user?
|
|
Notification.send_reaction_notification_without_delay(reaction, reaction.target_user)
|
|
Notification.send_reaction_notification_without_delay(reaction, reaction.reactable.organization) if reaction.reaction_on_organization_article?
|
|
"destroy"
|
|
end
|
|
|
|
def rate_article(reaction)
|
|
RatingVote.create(article_id: reaction.reactable_id,
|
|
group: "experience_level",
|
|
user_id: current_user.id,
|
|
context: "readinglist_reaction",
|
|
rating: current_user.experience_level)
|
|
end
|
|
end
|