docbrown/app/controllers/admin/users_controller.rb
Jeremy Friesen b4f17ed7dc
Allowing UserQuery to filter on multiple roles (#17919)
I had looked at using `User.with_any_roles` but that creates one User
query per role passed.  Which is not ideal, given that we want to have
an ActiveRecord::Relation object on which we'd paginate.

This adjustment does it's best to mimic Forem's role structure and
adhear to how Rolify builds the underlying query.

An astute reader will notice that in these specs we use `role: "admin"`
and `roles: ["Admin"]`; the primary reason relates the user interface
being worked on in forem/forem#17884.  It is my understanding that the
`role` approach and `roles` approach will be separated by a feature
flag (we're rolling out the `roles` approach).

Related to
- forem/forem#17491
- forem/forem#17884
2022-06-15 08:50:03 -04:00

351 lines
11 KiB
Ruby

module Admin
class UsersController < Admin::ApplicationController
layout "admin"
using StringToBoolean
USER_ALLOWED_PARAMS = %i[
new_note note_for_current_role user_status
merge_user_id add_credits remove_credits
add_org_credits remove_org_credits
organization_id identity_id
credit_action credit_amount
].freeze
EMAIL_ALLOWED_PARAMS = %i[
email_subject
email_body
].freeze
ATTRIBUTES_FOR_CSV = %i[
id name username email registered_at
].freeze
ATTRIBUTES_FOR_LAST_ACTIVITY = %i[
registered last_comment_at last_article_at latest_article_updated_at last_reacted_at profile_updated_at
last_moderation_notification last_notification_activity
].freeze
after_action only: %i[update user_status banish full_delete unpublish_all_articles merge] do
Audit::Logger.log(:moderator, current_user, params.dup)
end
def index
@users = Admin::UsersQuery.call(
relation: User.registered,
search: params[:search],
role: params[:role],
roles: params[:roles],
).page(params[:page]).per(50)
@organization_limit = 3
end
def edit
@user = User.find(params[:id])
@notes = @user.notes.order(created_at: :desc).limit(10).load
set_feedback_messages
set_related_reactions
end
def show
@user = User.find(params[:id])
set_current_tab(params[:tab])
set_banishable_user
set_feedback_messages
set_related_reactions
set_user_details
end
def update
@user = User.find(params[:id])
Credits::Manage.call(@user, credit_params)
add_note if user_params[:new_note]
redirect_to admin_user_path(params[:id])
end
def destroy
role = params[:role].to_sym
resource_type = params[:resource_type]
@user = User.find(params[:user_id])
response = ::Users::RemoveRole.call(user: @user,
role: role,
resource_type: resource_type,
admin: current_user)
if response.success
flash[:success] =
I18n.t("admin.users_controller.role_removed",
role: role.to_s.humanize.titlecase) # TODO: [@yheuhtozr] need better role i18n
else
flash[:danger] = response.error_message
end
redirect_to admin_user_path(params[:id])
end
def export
@users = User.registered.select(ATTRIBUTES_FOR_CSV + ATTRIBUTES_FOR_LAST_ACTIVITY).includes(:organizations)
respond_to do |format|
format.csv do
response.headers["Content-Type"] = "text/csv"
response.headers["Content-Disposition"] = "attachment; filename=users.csv"
render template: "admin/users/export"
end
end
end
def user_status
@user = User.find(params[:id])
begin
Moderator::ManageActivityAndRoles.handle_user_roles(admin: current_user, user: @user, user_params: user_params)
flash[:success] = I18n.t("admin.users_controller.updated")
rescue StandardError => e
flash[:danger] = e.message
end
if request.referer&.include?(admin_user_path(params[:id]))
redirect_to admin_user_path(params[:id])
else
redirect_to admin_users_path
end
end
def export_data
user = User.find(params[:id])
send_to_admin = params[:send_to_admin].to_boolean
if send_to_admin
email = ::ForemInstance.contact_email
receiver = "admin"
else
email = user.email
receiver = "user"
end
ExportContentWorker.perform_async(user.id, email)
flash[:success] = I18n.t("admin.users_controller.exported", receiver: receiver)
redirect_to admin_user_path(params[:id])
end
def banish
Moderator::BanishUserWorker.perform_async(current_user.id, params[:id].to_i)
flash[:success] = I18n.t("admin.users_controller.banished")
redirect_to admin_user_path(params[:id])
end
def full_delete
@user = User.find(params[:id])
begin
Moderator::DeleteUser.call(user: @user)
link = helpers.tag.a(I18n.t("admin.users_controller.the_page"), href: admin_gdpr_delete_requests_path,
data: { "no-instant" => true })
flash[:success] = I18n.t("admin.users_controller.full_delete_html",
user: @user.username,
email: @user.email.presence || I18n.t("admin.users_controller.no_email"),
id: @user.id,
the_page: link).html_safe # rubocop:disable Rails/OutputSafety
rescue StandardError => e
flash[:danger] = e.message
end
redirect_to admin_users_path
end
def unpublish_all_articles
Moderator::UnpublishAllArticlesWorker.perform_async(params[:id].to_i)
flash[:success] = I18n.t("admin.users_controller.unpublished")
redirect_to admin_user_path(params[:id])
end
def merge
@user = User.find(params[:id])
begin
Moderator::MergeUser.call(admin: current_user, keep_user: @user, delete_user_id: user_params["merge_user_id"])
rescue StandardError => e
flash[:danger] = e.message
end
redirect_to admin_user_path(params[:id])
end
def remove_identity
identity = Identity.find(user_params[:identity_id])
@user = identity.user
begin
identity.destroy
@user.update("#{identity.provider}_username" => nil)
# GitHub repositories are tied with the existence of the GitHub identity
# as we use the user's GitHub token to fetch them from the API.
# We should delete them when a user unlinks their GitHub account.
@user.github_repos.destroy_all if identity.provider.to_sym == :github
flash[:success] =
I18n.t("admin.users_controller.identity_removed",
provider: identity.provider.capitalize)
rescue StandardError => e
flash[:danger] = e.message
end
redirect_to admin_user_path(params[:id])
end
def send_email
email_params = {
email_body: send_email_params[:email_body],
email_subject: send_email_params[:email_subject],
user_id: params[:id]
}
if NotifyMailer.with(email_params).user_contact_email.deliver_now
respond_to do |format|
message = I18n.t("admin.users_controller.email_sent")
format.html do
flash[:success] = message
redirect_back(fallback_location: admin_user_path(params[:id]))
end
format.js { render json: { result: message }, content_type: "application/json" }
end
else
respond_to do |format|
message = I18n.t("admin.users_controller.email_fail")
format.html do
flash[:danger] = message
redirect_back(fallback_location: admin_user_path(params[:id]))
end
format.js do
render json: { error: message },
content_type: "application/json",
status: :service_unavailable
end
end
end
rescue ActionController::ParameterMissing
respond_to do |format|
format.json do
render json: { error: I18n.t("admin.users_controller.parameter_missing") },
content_type: "application/json",
status: :unprocessable_entity
end
end
end
def verify_email_ownership
if VerificationMailer.with(user_id: params[:id]).account_ownership_verification_email.deliver_now
respond_to do |format|
message = I18n.t("admin.users_controller.verify_sent")
format.html do
flash[:success] = message
redirect_back(fallback_location: admin_user_path(params[:id]))
end
format.js { render json: { result: message }, content_type: "application/json" }
end
else
message = I18n.t("admin.users_controller.email_fail")
respond_to do |format|
format.html do
flash[:danger] = message
redirect_back(fallback_location: admin_user_path(params[:id]))
end
format.js { render json: { error: message }, content_type: "application/json", status: :service_unavailable }
end
end
end
def unlock_access
@user = User.find(params[:id])
@user.unlock_access!
flash[:success] = I18n.t("admin.users_controller.unlocked")
redirect_to admin_user_path(@user)
end
private
def set_user_details
@organizations = @user.organizations.order(:name)
@notes = @user.notes.order(created_at: :desc).limit(10)
@organization_memberships = @user.organization_memberships
.joins(:organization)
.order("organizations.name" => :asc)
.includes(:organization)
@last_email_verification_date = EmailAuthorization.last_verification_date(@user)
render :show
end
def add_note
Note.create(
author_id: current_user.id,
noteable_id: @user.id,
noteable_type: "User",
reason: "misc_note",
content: user_params[:new_note],
)
end
def set_feedback_messages
@related_reports = FeedbackMessage.where(id: @user.reporter_feedback_messages.ids)
.or(FeedbackMessage.where(id: @user.affected_feedback_messages.ids))
.or(FeedbackMessage.where(id: @user.offender_feedback_messages.ids))
.order(created_at: :desc).limit(15)
end
def set_related_reactions
user_article_ids = @user.articles.ids
user_comment_ids = @user.comments.ids
@related_vomit_reactions =
Reaction.where(reactable_type: "Comment", reactable_id: user_comment_ids, category: "vomit")
.or(Reaction.where(reactable_type: "Article", reactable_id: user_article_ids, category: "vomit"))
.or(Reaction.where(reactable_type: "User", reactable_id: @user.id, category: "vomit"))
.includes(:reactable)
.order(created_at: :desc).limit(15)
end
def user_params
params.require(:user).permit(USER_ALLOWED_PARAMS)
end
def send_email_params
params.require(EMAIL_ALLOWED_PARAMS)
params.permit(EMAIL_ALLOWED_PARAMS)
end
def credit_params
credit_params = {}
case user_params[:credit_action]
when "Add"
credit_params[:add_credits] = user_params[:credit_amount]
flash[:success] = I18n.t("admin.users_controller.credits_added")
when "Remove"
credit_params[:remove_credits] = user_params[:credit_amount]
flash[:success] = I18n.t("admin.users_controller.credits_removed")
else
return user_params
end
credit_params
end
def set_current_tab(current_tab = "overview")
@current_tab = if current_tab.in? Constants::UserDetails::TAB_LIST.map(&:downcase)
current_tab
else
"overview"
end
end
def set_banishable_user
@banishable_user = (@user.comments.where("created_at < ?", 100.days.ago).empty? &&
@user.created_at < 100.days.ago) || current_user.super_admin? || current_user.support_admin?
end
end
end