* db * Add an iframe to host mod actions menu This iframe is probably the fastest way to get a working mod actions menu without rewriting existing code. We may eventually re-write this, but based on Ben's recommendation we are going to go with an iframe because it gets us a working feature quickly and it seems to be serving us well in other parts of the application. * Add the mod actions menu button to articles When the user is "trusted" they will see a small badge on the lower right-hand side of the screen. Clicking the icon reveals the mod actions menu. * Add header to actions panel (#7395) * Update iframe border * Add 'Moderate Post' header and button * Use inline SVG over <img src='svg'> * Use Crayons variables for units * Use flexbox to center chevron * Add accidentally deleted positioning * Add chevron-right.svg * Use a partial for actions panel and revert /mod page * Load actions panel in iframe and not /mod * Style UpVote, DownVote and Vomit buttons (#7421) * Add a iframe to host mod actions menu * Up and Downvote Icons * Starting with vomit button * Completed UI; interactions pending * Incorporated Andy changes; making mod action-panel * space reactions away from header * UI tweaks and corrections 1 * Fixing styles to match crayons * choose pure white color css variable * Add thumbs-up emoji; address Lisa comments * Trying to fix reactions interactions * remove clearThumbReactions function Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Add bottom layout for actions panel (#7501) * WIP for Lisa * Finalize mod actions bottom section * Use showing instead of hidden for transition * Add new optimized SVGs * Fix weird height issues for actions panel * Move hover styles below the cascade, yo * Use margin-top for sticky footer over height: 100% * Move initialize actions panel button to Webpack (#7506) * Move actions panel JS code to webpack * Refactor and use destructuring Thanks @nickytonline! Co-Authored-By: Nick Taylor <nick@iamdeveloper.com> * Rename file Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Configure Experience Level options for Mod Action Panel (#7507) * Add a iframe to host mod actions menu * Click interactions (no toggle of dropdowns yet) Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Fix the iframe's path (#7509) * Toggle the 'Set Experience Level' menu on button-click (#7529) * Add a iframe to host mod actions menu * Add set-exp dropdown functionality * WIP: making exp level updates async * Completes async implementation * Fix controller for "actions_panel" and "/mod" Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Add close button interaction (#7552) * Rename actions panel pack file * Add close button toggle to actions panel * DB update * Move more code to Webpack and add conditional tag adjustments section (#7652) * Move JS code to Webpack * Clean up a few style issues * Add styling for tag adjustments * Add active/inactive functionality * Add conditional rendering for tag adjustments section * Add missing variable oops (#7673) * update with upstream * Add missing subtract svg (#7695) * Create 'Flag User' modal (#7626) * Add a iframe to host mod actions menu * Starting task * Phase 1 * Fix "Close Modal" icon * Implement open modal * Working on all "Close Modal" actions * Almost-complete implementation * Remove Abuse Report snackbar * Delete unneeded JS code * Remove "How does this work?" links * Link to Community Moderation page * Addressing code review corrections * Update api call to use "request" method * Make else path clearer in api call Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> * Mod Actions Panel: Prevent nonsensical reaction pairs (#7699) * Still implementing * Completed Implementation * Address code review feednack; refactor "create" method * Move scss variables to native-css (for ModPanel code only) * Correct missing native css * update to native-css again * More sccs -> native-css fixes * Adjust ModPanel Button position on mobile (#7744) * Adjusted ModPanel position on mobile * Better adjustment * Fix phantom schema.rb changes * Fix phantom schema.rb changes * Finalize tag adjustments (#7740) * Finalize tag adjustments and flow * Fix logic for rendering adjust tags options * Clean up alert response * Rename user to trusted user * Use new CSS variables * Add/remove tag on article's tag section after adjusting * Fix logic for admin tag input and some other niceties * Final bug fixes mod panel (#7772) * Use correct boolean statement * Clear the value of the textarea not div * Use the correct attribute * Use actions panel for /mod page (#7775) * Fix loading logic with tag adjust button conditional render * Link to tag moderation for adjust tags section * Use top over window.parent Thanks @nickytonline! Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Use Webpack alias to import Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Use place-items shorthand over align-items + justify-content Thanks @nickytonline! Co-authored-by: Nick Taylor <nick@iamdeveloper.com> * Fix typo for utilities * Add new line to end of file * Disable linter for use for top function * Use align-items and justify-content center * Update SVGs to spec * Remove unnecessary height and use spacing units for open button * Remove accidentally added comment * Error handling when selecting reactions; optimize "reactions.each" loop * Add comment about clearing innerHTML * Revert schema back to master * Use each since there should only be two reactions * Fix typo oops * Add missing height oops * Return if there are no reactions to be destroyed * Center path with GUI tool shrug * chose more appropriate name for JS pack; rewrote api calls using async-await * Rename variables and methods and check categories before clearing * Use addSnackbarItem instead of hardcoded version * Place articleModerationTools pack behind user_signed_in * Correct omission * Implement dynamic imports for ModPanel tools * Implemented async/await; added snackbar message for null response use-case * Fix the "/article/mod" page; make sure all reactions, vomit-user and exp-level working as expected * Update language to reflect new UI * Close the modal after the "Confirm Action" button is clicked * Change text for Spam/Abuse button * Fix schema to use with master * Update schema to master again * Use crayons button for actions panel button * UI tweak to play nicer with site themes * Make actions panel work for dev.to/mod * Add snackbar for reactions * Update how does this work links * Add "thumbsup" reactions to logging * Add thumbsup to categories * Add external link icon * Changes to reflect "Privileged" and not "Negative" reactions; spec changes pending * Updated instance variable * Starting to update the specs * Update "thumbsup" points from 10 to 5 * Still working on both requests * Embracing an easier implemantation (thanks Andy!) * Add tests for actions panel * Add some basic tests for actions panel * Change to flag to admins * Indicate that 'Reason for Tag adjust' is required Co-authored-by: Mac Siri <krairit.siri@gmail.com> * Rename negative_reactions to privileged * Update rating level to use correct values * Use a different display value than backend value * Don't show adjust tags for trusted users * Add specs for moderation_routes for admin and super_admin Co-authored-by: Arit Amana <msarit@gmail.com> Co-authored-by: jacobherrington <jacobherringtondeveloper@gmail.com> Co-authored-by: Arit Amana <32520970+msarit@users.noreply.github.com> Co-authored-by: Nick Taylor <nick@iamdeveloper.com> Co-authored-by: Mac Siri <krairit.siri@gmail.com>
177 lines
5.5 KiB
Ruby
177 lines
5.5 KiB
Ruby
class ReactionsController < ApplicationController
|
|
before_action :set_cache_control_headers, only: [:index], unless: -> { current_user }
|
|
before_action :authorize_for_reaction, :check_limit, only: [:create]
|
|
after_action :verify_authorized
|
|
|
|
NEGATIVE_CATEGORIES = %w[thumbsdown vomit].freeze
|
|
MODERATION_CATEGORIES = %w[thumbsup thumbsdown vomit].freeze
|
|
|
|
def index
|
|
skip_authorization
|
|
|
|
if params[:article_id]
|
|
id = params[:article_id]
|
|
|
|
reactions = if session_current_user_id
|
|
Reaction.public_category.
|
|
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 public_reactions_count])
|
|
|
|
reaction_counts = comments.map do |comment|
|
|
{ id: comment.id, count: comment.public_reactions_count }
|
|
end
|
|
|
|
reactions = if session_current_user_id
|
|
comment_ids = reaction_counts.map { |rc| rc[:id] }
|
|
cached_user_public_reactions(current_user).where(reactable_id: comment_ids)
|
|
else
|
|
Reaction.none
|
|
end
|
|
|
|
result = { public_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
|
|
remove_count_cache_key
|
|
|
|
if params[:reactable_type] == "Article" && params[:category].in?(MODERATION_CATEGORIES)
|
|
clear_moderator_reactions(
|
|
params[:reactable_id],
|
|
params[:reactable_type],
|
|
current_user,
|
|
params[:category],
|
|
)
|
|
end
|
|
|
|
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)
|
|
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)
|
|
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 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
|
|
end
|
|
render json: { result: result, category: category }
|
|
end
|
|
|
|
def cached_user_public_reactions(user)
|
|
Rails.cache.fetch("cached-user-reactions-#{user.id}-#{user.public_reactions_count}", expires_in: 24.hours) do
|
|
user.reactions.public_category
|
|
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)
|
|
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
|
|
|
|
def clear_moderator_reactions(id, type, mod, category)
|
|
reactions = if category == "thumbsup"
|
|
Reaction.where(reactable_id: id, reactable_type: type, user: mod).where.not(category: category)
|
|
elsif category.in?(NEGATIVE_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
|
|
|
|
def authorize_for_reaction
|
|
authorize Reaction
|
|
end
|
|
|
|
def remove_count_cache_key
|
|
return unless params[:reactable_type] == "Article"
|
|
|
|
Rails.cache.delete "count_for_reactable-Article-#{params[:reactable_id]}"
|
|
end
|
|
end
|