forked from treehouse/mastodon
Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `app/views/admin/reports/show.html.haml`: Conflicts due to glitch-soc's theming system.rebase/4.0.0rc2
commit
b3bf32a21e
|
@ -14,7 +14,7 @@ module Admin
|
||||||
else
|
else
|
||||||
@account = @account_moderation_note.target_account
|
@account = @account_moderation_note.target_account
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
@warnings = @account.targeted_account_warnings.latest.custom
|
@warnings = @account.strikes.custom.latest
|
||||||
|
|
||||||
render template: 'admin/accounts/show'
|
render template: 'admin/accounts/show'
|
||||||
end
|
end
|
||||||
|
|
|
@ -28,7 +28,7 @@ module Admin
|
||||||
@deletion_request = @account.deletion_request
|
@deletion_request = @account.deletion_request
|
||||||
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
|
||||||
@moderation_notes = @account.targeted_moderation_notes.latest
|
@moderation_notes = @account.targeted_moderation_notes.latest
|
||||||
@warnings = @account.targeted_account_warnings.latest.custom
|
@warnings = @account.strikes.custom.latest
|
||||||
@domain_block = DomainBlock.rule_for(@account.domain)
|
@domain_block = DomainBlock.rule_for(@account.domain)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -14,20 +14,17 @@ module Admin
|
||||||
if params[:create_and_resolve]
|
if params[:create_and_resolve]
|
||||||
@report.resolve!(current_account)
|
@report.resolve!(current_account)
|
||||||
log_action :resolve, @report
|
log_action :resolve, @report
|
||||||
|
elsif params[:create_and_unresolve]
|
||||||
redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
|
|
||||||
return
|
|
||||||
end
|
|
||||||
|
|
||||||
if params[:create_and_unresolve]
|
|
||||||
@report.unresolve!
|
@report.unresolve!
|
||||||
log_action :reopen, @report
|
log_action :reopen, @report
|
||||||
end
|
end
|
||||||
|
|
||||||
redirect_to admin_report_path(@report), notice: I18n.t('admin.report_notes.created_msg')
|
redirect_to after_create_redirect_path, notice: I18n.t('admin.report_notes.created_msg')
|
||||||
else
|
else
|
||||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
@report_notes = @report.notes.includes(:account).order(id: :desc)
|
||||||
@form = Form::StatusBatch.new
|
@action_logs = @report.history.includes(:target)
|
||||||
|
@form = Admin::StatusBatchAction.new
|
||||||
|
@statuses = @report.statuses.with_includes
|
||||||
|
|
||||||
render template: 'admin/reports/show'
|
render template: 'admin/reports/show'
|
||||||
end
|
end
|
||||||
|
@ -41,6 +38,14 @@ module Admin
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
def after_create_redirect_path
|
||||||
|
if params[:create_and_resolve]
|
||||||
|
admin_reports_path
|
||||||
|
else
|
||||||
|
admin_report_path(@report)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
def resource_params
|
def resource_params
|
||||||
params.require(:report_note).permit(
|
params.require(:report_note).permit(
|
||||||
:content,
|
:content,
|
||||||
|
|
|
@ -1,44 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
module Admin
|
|
||||||
class ReportedStatusesController < BaseController
|
|
||||||
before_action :set_report
|
|
||||||
|
|
||||||
def create
|
|
||||||
authorize :status, :update?
|
|
||||||
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
|
||||||
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
|
||||||
|
|
||||||
redirect_to admin_report_path(@report)
|
|
||||||
rescue ActionController::ParameterMissing
|
|
||||||
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
|
||||||
|
|
||||||
redirect_to admin_report_path(@report)
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def status_params
|
|
||||||
params.require(:status).permit(:sensitive)
|
|
||||||
end
|
|
||||||
|
|
||||||
def form_status_batch_params
|
|
||||||
params.require(:form_status_batch).permit(status_ids: [])
|
|
||||||
end
|
|
||||||
|
|
||||||
def action_from_button
|
|
||||||
if params[:nsfw_on]
|
|
||||||
'nsfw_on'
|
|
||||||
elsif params[:nsfw_off]
|
|
||||||
'nsfw_off'
|
|
||||||
elsif params[:delete]
|
|
||||||
'delete'
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
def set_report
|
|
||||||
@report = Report.find(params[:report_id])
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -13,8 +13,10 @@ module Admin
|
||||||
authorize @report, :show?
|
authorize @report, :show?
|
||||||
|
|
||||||
@report_note = @report.notes.new
|
@report_note = @report.notes.new
|
||||||
@report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
|
@report_notes = @report.notes.includes(:account).order(id: :desc)
|
||||||
@form = Form::StatusBatch.new
|
@action_logs = @report.history.includes(:target)
|
||||||
|
@form = Admin::StatusBatchAction.new
|
||||||
|
@statuses = @report.statuses.with_includes
|
||||||
end
|
end
|
||||||
|
|
||||||
def assign_to_self
|
def assign_to_self
|
||||||
|
|
|
@ -2,71 +2,57 @@
|
||||||
|
|
||||||
module Admin
|
module Admin
|
||||||
class StatusesController < BaseController
|
class StatusesController < BaseController
|
||||||
helper_method :current_params
|
|
||||||
|
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
before_action :set_statuses
|
||||||
|
|
||||||
PER_PAGE = 20
|
PER_PAGE = 20
|
||||||
|
|
||||||
def index
|
def index
|
||||||
authorize :status, :index?
|
authorize :status, :index?
|
||||||
|
|
||||||
@statuses = @account.statuses.where(visibility: [:public, :unlisted])
|
@status_batch_action = Admin::StatusBatchAction.new
|
||||||
|
|
||||||
if params[:media]
|
|
||||||
@statuses = @statuses.merge(Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)).reorder('statuses.id desc')
|
|
||||||
end
|
end
|
||||||
|
|
||||||
@statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
|
def batch
|
||||||
@form = Form::StatusBatch.new
|
@status_batch_action = Admin::StatusBatchAction.new(admin_status_batch_action_params.merge(current_account: current_account, report_id: params[:report_id], type: action_from_button))
|
||||||
end
|
@status_batch_action.save!
|
||||||
|
|
||||||
def show
|
|
||||||
authorize :status, :index?
|
|
||||||
|
|
||||||
@statuses = @account.statuses.where(id: params[:id])
|
|
||||||
authorize @statuses.first, :show?
|
|
||||||
|
|
||||||
@form = Form::StatusBatch.new
|
|
||||||
end
|
|
||||||
|
|
||||||
def create
|
|
||||||
authorize :status, :update?
|
|
||||||
|
|
||||||
@form = Form::StatusBatch.new(form_status_batch_params.merge(current_account: current_account, action: action_from_button))
|
|
||||||
flash[:alert] = I18n.t('admin.statuses.failed_to_execute') unless @form.save
|
|
||||||
|
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
|
||||||
rescue ActionController::ParameterMissing
|
rescue ActionController::ParameterMissing
|
||||||
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
|
||||||
|
ensure
|
||||||
redirect_to admin_account_statuses_path(@account.id, current_params)
|
redirect_to after_create_redirect_path
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
def form_status_batch_params
|
def admin_status_batch_action_params
|
||||||
params.require(:form_status_batch).permit(:action, status_ids: [])
|
params.require(:admin_status_batch_action).permit(status_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
|
def after_create_redirect_path
|
||||||
|
if @status_batch_action.report_id.present?
|
||||||
|
admin_report_path(@status_batch_action.report_id)
|
||||||
|
else
|
||||||
|
admin_account_statuses_path(params[:account_id], current_params)
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_account
|
def set_account
|
||||||
@account = Account.find(params[:account_id])
|
@account = Account.find(params[:account_id])
|
||||||
end
|
end
|
||||||
|
|
||||||
def current_params
|
def set_statuses
|
||||||
page = (params[:page] || 1).to_i
|
@statuses = Admin::StatusFilter.new(@account, filter_params).results.preload(:application, :preloadable_poll, :media_attachments, active_mentions: :account, reblog: [:account, :application, :preloadable_poll, :media_attachments, active_mentions: :account]).page(params[:page]).per(PER_PAGE)
|
||||||
|
end
|
||||||
|
|
||||||
{
|
def filter_params
|
||||||
media: params[:media],
|
params.slice(*Admin::StatusFilter::KEYS).permit(*Admin::StatusFilter::KEYS)
|
||||||
page: page > 1 && page,
|
|
||||||
}.select { |_, value| value.present? }
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def action_from_button
|
def action_from_button
|
||||||
if params[:nsfw_on]
|
if params[:report]
|
||||||
'nsfw_on'
|
'report'
|
||||||
elsif params[:nsfw_off]
|
elsif params[:remove_from_report]
|
||||||
'nsfw_off'
|
'remove_from_report'
|
||||||
elsif params[:delete]
|
elsif params[:delete]
|
||||||
'delete'
|
'delete'
|
||||||
end
|
end
|
||||||
|
|
|
@ -1,7 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Admin::AccountActionsController < Api::BaseController
|
class Api::V1::Admin::AccountActionsController < Api::BaseController
|
||||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_account
|
before_action :set_account
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Admin::AccountsController < Api::BaseController
|
class Api::V1::Admin::AccountsController < Api::BaseController
|
||||||
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
include Authorization
|
include Authorization
|
||||||
include AccountableConcern
|
include AccountableConcern
|
||||||
|
|
||||||
LIMIT = 100
|
LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:accounts' }, only: [:index, :show]
|
||||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_accounts, only: :index
|
before_action :set_accounts, only: :index
|
||||||
before_action :set_account, except: :index
|
before_action :set_account, except: :index
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class Api::V1::Admin::DimensionsController < Api::BaseController
|
class Api::V1::Admin::DimensionsController < Api::BaseController
|
||||||
protect_from_forgery with: :exception
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_dimensions
|
before_action :set_dimensions
|
||||||
|
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class Api::V1::Admin::MeasuresController < Api::BaseController
|
class Api::V1::Admin::MeasuresController < Api::BaseController
|
||||||
protect_from_forgery with: :exception
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_measures
|
before_action :set_measures
|
||||||
|
|
||||||
|
|
|
@ -1,13 +1,15 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Admin::ReportsController < Api::BaseController
|
class Api::V1::Admin::ReportsController < Api::BaseController
|
||||||
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
include Authorization
|
include Authorization
|
||||||
include AccountableConcern
|
include AccountableConcern
|
||||||
|
|
||||||
LIMIT = 100
|
LIMIT = 100
|
||||||
|
|
||||||
before_action -> { doorkeeper_authorize! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:read', :'admin:read:reports' }, only: [:index, :show]
|
||||||
before_action -> { doorkeeper_authorize! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
|
before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_reports, only: :index
|
before_action :set_reports, only: :index
|
||||||
before_action :set_report, except: :index
|
before_action :set_report, except: :index
|
||||||
|
@ -32,6 +34,12 @@ class Api::V1::Admin::ReportsController < Api::BaseController
|
||||||
render json: @report, serializer: REST::Admin::ReportSerializer
|
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def update
|
||||||
|
authorize @report, :update?
|
||||||
|
@report.update!(report_params)
|
||||||
|
render json: @report, serializer: REST::Admin::ReportSerializer
|
||||||
|
end
|
||||||
|
|
||||||
def assign_to_self
|
def assign_to_self
|
||||||
authorize @report, :update?
|
authorize @report, :update?
|
||||||
@report.update!(assigned_account_id: current_account.id)
|
@report.update!(assigned_account_id: current_account.id)
|
||||||
|
@ -74,6 +82,10 @@ class Api::V1::Admin::ReportsController < Api::BaseController
|
||||||
ReportFilter.new(filter_params).results
|
ReportFilter.new(filter_params).results
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def report_params
|
||||||
|
params.permit(:category, rule_ids: [])
|
||||||
|
end
|
||||||
|
|
||||||
def filter_params
|
def filter_params
|
||||||
params.permit(*FILTER_PARAMS)
|
params.permit(*FILTER_PARAMS)
|
||||||
end
|
end
|
||||||
|
|
|
@ -3,6 +3,7 @@
|
||||||
class Api::V1::Admin::RetentionController < Api::BaseController
|
class Api::V1::Admin::RetentionController < Api::BaseController
|
||||||
protect_from_forgery with: :exception
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_cohorts
|
before_action :set_cohorts
|
||||||
|
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class Api::V1::Admin::Trends::TagsController < Api::BaseController
|
class Api::V1::Admin::Trends::TagsController < Api::BaseController
|
||||||
|
protect_from_forgery with: :exception
|
||||||
|
|
||||||
|
before_action -> { authorize_if_got_token! :'admin:read' }
|
||||||
before_action :require_staff!
|
before_action :require_staff!
|
||||||
before_action :set_tags
|
before_action :set_tags
|
||||||
|
|
||||||
|
|
|
@ -13,6 +13,7 @@ module Admin::FilterHelper
|
||||||
RelationshipFilter::KEYS,
|
RelationshipFilter::KEYS,
|
||||||
AnnouncementFilter::KEYS,
|
AnnouncementFilter::KEYS,
|
||||||
Admin::ActionLogFilter::KEYS,
|
Admin::ActionLogFilter::KEYS,
|
||||||
|
Admin::StatusFilter::KEYS,
|
||||||
].flatten.freeze
|
].flatten.freeze
|
||||||
|
|
||||||
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
|
||||||
|
|
|
@ -0,0 +1,159 @@
|
||||||
|
import React from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import api from 'mastodon/api';
|
||||||
|
import { injectIntl, defineMessages } from 'react-intl';
|
||||||
|
import classNames from 'classnames';
|
||||||
|
|
||||||
|
const messages = defineMessages({
|
||||||
|
other: { id: 'report.categories.other', defaultMessage: 'Other' },
|
||||||
|
spam: { id: 'report.categories.spam', defaultMessage: 'Spam' },
|
||||||
|
violation: { id: 'report.categories.violation', defaultMessage: 'Content violates one or more server rules' },
|
||||||
|
});
|
||||||
|
|
||||||
|
class Category extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
text: PropTypes.string.isRequired,
|
||||||
|
selected: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onSelect: PropTypes.func,
|
||||||
|
children: PropTypes.node,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClick = () => {
|
||||||
|
const { id, disabled, onSelect } = this.props;
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
onSelect(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { id, text, disabled, selected, children } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div tabIndex='0' role='button' className={classNames('report-reason-selector__category', { selected, disabled })} onClick={this.handleClick}>
|
||||||
|
{selected && <input type='hidden' name='report[category]' value={id} />}
|
||||||
|
|
||||||
|
<div className='report-reason-selector__category__label'>
|
||||||
|
<span className={classNames('poll__input', { active: selected, disabled })} />
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{(selected && children) && (
|
||||||
|
<div className='report-reason-selector__category__rules'>
|
||||||
|
{children}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
class Rule extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
text: PropTypes.string.isRequired,
|
||||||
|
selected: PropTypes.bool,
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
onToggle: PropTypes.func,
|
||||||
|
};
|
||||||
|
|
||||||
|
handleClick = () => {
|
||||||
|
const { id, disabled, onToggle } = this.props;
|
||||||
|
|
||||||
|
if (!disabled) {
|
||||||
|
onToggle(id);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { id, text, disabled, selected } = this.props;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div tabIndex='0' role='button' className={classNames('report-reason-selector__rule', { selected, disabled })} onClick={this.handleClick}>
|
||||||
|
<span className={classNames('poll__input', { checkbox: true, active: selected, disabled })} />
|
||||||
|
{selected && <input type='hidden' name='report[rule_ids][]' value={id} />}
|
||||||
|
{text}
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
export default @injectIntl
|
||||||
|
class ReportReasonSelector extends React.PureComponent {
|
||||||
|
|
||||||
|
static propTypes = {
|
||||||
|
id: PropTypes.string.isRequired,
|
||||||
|
category: PropTypes.string.isRequired,
|
||||||
|
rule_ids: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
disabled: PropTypes.bool,
|
||||||
|
intl: PropTypes.object.isRequired,
|
||||||
|
};
|
||||||
|
|
||||||
|
state = {
|
||||||
|
category: this.props.category,
|
||||||
|
rule_ids: this.props.rule_ids || [],
|
||||||
|
rules: [],
|
||||||
|
};
|
||||||
|
|
||||||
|
componentDidMount() {
|
||||||
|
api().get('/api/v1/instance').then(res => {
|
||||||
|
this.setState({
|
||||||
|
rules: res.data.rules,
|
||||||
|
});
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
_save = () => {
|
||||||
|
const { id, disabled } = this.props;
|
||||||
|
const { category, rule_ids } = this.state;
|
||||||
|
|
||||||
|
if (disabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
api().put(`/api/v1/admin/reports/${id}`, {
|
||||||
|
category,
|
||||||
|
rule_ids,
|
||||||
|
}).catch(err => {
|
||||||
|
console.error(err);
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
handleSelect = id => {
|
||||||
|
this.setState({ category: id }, () => this._save());
|
||||||
|
};
|
||||||
|
|
||||||
|
handleToggle = id => {
|
||||||
|
const { rule_ids } = this.state;
|
||||||
|
|
||||||
|
if (rule_ids.includes(id)) {
|
||||||
|
this.setState({ rule_ids: rule_ids.filter(x => x !== id ) }, () => this._save());
|
||||||
|
} else {
|
||||||
|
this.setState({ rule_ids: [...rule_ids, id] }, () => this._save());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
render () {
|
||||||
|
const { disabled, intl } = this.props;
|
||||||
|
const { rules, category, rule_ids } = this.state;
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className='report-reason-selector'>
|
||||||
|
<Category id='other' text={intl.formatMessage(messages.other)} selected={category === 'other'} onSelect={this.handleSelect} disabled={disabled} />
|
||||||
|
<Category id='spam' text={intl.formatMessage(messages.spam)} selected={category === 'spam'} onSelect={this.handleSelect} disabled={disabled} />
|
||||||
|
<Category id='violation' text={intl.formatMessage(messages.violation)} selected={category === 'violation'} onSelect={this.handleSelect} disabled={disabled}>
|
||||||
|
{rules.map(rule => <Rule key={rule.id} id={rule.id} text={rule.text} selected={rule_ids.includes(rule.id)} onToggle={this.handleToggle} disabled={disabled} />)}
|
||||||
|
</Category>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -291,7 +291,7 @@ class StatusActionBar extends ImmutablePureComponent {
|
||||||
if (isStaff) {
|
if (isStaff) {
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
menu.push({ text: intl.formatMessage(messages.admin_account, { name: account.get('username') }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses?id=${status.get('id')}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -245,7 +245,7 @@ class ActionBar extends React.PureComponent {
|
||||||
if (isStaff) {
|
if (isStaff) {
|
||||||
menu.push(null);
|
menu.push(null);
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
menu.push({ text: intl.formatMessage(messages.admin_account, { name: status.getIn(['account', 'username']) }), href: `/admin/accounts/${status.getIn(['account', 'id'])}` });
|
||||||
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses/${status.get('id')}` });
|
menu.push({ text: intl.formatMessage(messages.admin_status), href: `/admin/accounts/${status.getIn(['account', 'id'])}/statuses?id=${status.get('id')}` });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -533,6 +533,10 @@ ul {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ul.rules-list {
|
||||||
|
padding-top: 0;
|
||||||
|
}
|
||||||
|
|
||||||
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) {
|
@media only screen and (min-device-width: 768px) and (max-device-width: 1024px) and (orientation: landscape) {
|
||||||
body {
|
body {
|
||||||
min-height: 1024px !important;
|
min-height: 1024px !important;
|
||||||
|
|
|
@ -595,39 +595,44 @@ body,
|
||||||
|
|
||||||
.log-entry {
|
.log-entry {
|
||||||
line-height: 20px;
|
line-height: 20px;
|
||||||
padding: 15px 0;
|
padding: 15px;
|
||||||
|
padding-left: 15px * 2 + 40px;
|
||||||
background: $ui-base-color;
|
background: $ui-base-color;
|
||||||
border-bottom: 1px solid lighten($ui-base-color, 4%);
|
border-bottom: 1px solid darken($ui-base-color, 8%);
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
&:last-child {
|
&:last-child {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
border-bottom: 0;
|
border-bottom: 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background: lighten($ui-base-color, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
&__header {
|
&__header {
|
||||||
display: flex;
|
|
||||||
justify-content: flex-start;
|
|
||||||
align-items: center;
|
|
||||||
color: $darker-text-color;
|
color: $darker-text-color;
|
||||||
font-size: 14px;
|
font-size: 14px;
|
||||||
padding: 0 10px;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
&__avatar {
|
&__avatar {
|
||||||
margin-right: 10px;
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 15px;
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
display: block;
|
border-radius: 4px;
|
||||||
margin: 0;
|
|
||||||
border-radius: 50%;
|
|
||||||
width: 40px;
|
width: 40px;
|
||||||
height: 40px;
|
height: 40px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__content {
|
|
||||||
max-width: calc(100% - 90px);
|
|
||||||
}
|
|
||||||
|
|
||||||
&__title {
|
&__title {
|
||||||
word-wrap: break-word;
|
word-wrap: break-word;
|
||||||
}
|
}
|
||||||
|
@ -643,6 +648,14 @@ body,
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
font-weight: 500;
|
font-weight: 500;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
a {
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
a.name-tag,
|
a.name-tag,
|
||||||
|
@ -671,8 +684,9 @@ a.inline-name-tag,
|
||||||
|
|
||||||
a.name-tag,
|
a.name-tag,
|
||||||
.name-tag {
|
.name-tag {
|
||||||
display: flex;
|
display: inline-flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
|
vertical-align: top;
|
||||||
|
|
||||||
.avatar {
|
.avatar {
|
||||||
display: block;
|
display: block;
|
||||||
|
@ -1130,3 +1144,287 @@ a.sparkline {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.report-reason-selector {
|
||||||
|
border-radius: 4px;
|
||||||
|
background: $ui-base-color;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&__category {
|
||||||
|
cursor: pointer;
|
||||||
|
border-bottom: 1px solid darken($ui-base-color, 8%);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__label {
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rules {
|
||||||
|
margin-left: 30px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__rule {
|
||||||
|
cursor: pointer;
|
||||||
|
padding: 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-header {
|
||||||
|
display: grid;
|
||||||
|
grid-gap: 15px;
|
||||||
|
grid-template-columns: minmax(0, 1fr) 300px;
|
||||||
|
|
||||||
|
&__details {
|
||||||
|
&__item {
|
||||||
|
border-bottom: 1px solid lighten($ui-base-color, 8%);
|
||||||
|
padding: 15px 0;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
font-weight: 600;
|
||||||
|
padding: 4px 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&--horizontal {
|
||||||
|
display: grid;
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
grid-auto-flow: column;
|
||||||
|
|
||||||
|
.report-header__details__item {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.account-card {
|
||||||
|
background: $ui-base-color;
|
||||||
|
border-radius: 4px;
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
padding: 4px;
|
||||||
|
border-radius: 4px;
|
||||||
|
height: 128px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
width: 100%;
|
||||||
|
height: 100%;
|
||||||
|
object-fit: cover;
|
||||||
|
background: darken($ui-base-color, 8%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__title {
|
||||||
|
margin-top: -25px;
|
||||||
|
display: flex;
|
||||||
|
align-items: flex-end;
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
padding: 15px;
|
||||||
|
|
||||||
|
img {
|
||||||
|
display: block;
|
||||||
|
margin: 0;
|
||||||
|
width: 56px;
|
||||||
|
height: 56px;
|
||||||
|
background: darken($ui-base-color, 8%);
|
||||||
|
border-radius: 8px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.display-name {
|
||||||
|
color: $darker-text-color;
|
||||||
|
padding-bottom: 15px;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
bdi {
|
||||||
|
display: block;
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-weight: 500;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__bio {
|
||||||
|
padding: 0 15px;
|
||||||
|
overflow: hidden;
|
||||||
|
text-overflow: ellipsis;
|
||||||
|
word-wrap: break-word;
|
||||||
|
max-height: 18px * 2;
|
||||||
|
position: relative;
|
||||||
|
|
||||||
|
&::after {
|
||||||
|
display: block;
|
||||||
|
content: "";
|
||||||
|
width: 50px;
|
||||||
|
height: 18px;
|
||||||
|
position: absolute;
|
||||||
|
bottom: 0;
|
||||||
|
right: 15px;
|
||||||
|
background: linear-gradient(to left, $ui-base-color, transparent);
|
||||||
|
pointer-events: none;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
padding-top: 10px;
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
padding: 0 15px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__counters {
|
||||||
|
flex: 1 1 auto;
|
||||||
|
display: grid;
|
||||||
|
grid-auto-columns: minmax(0, 1fr);
|
||||||
|
grid-auto-flow: column;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
padding: 15px;
|
||||||
|
text-align: center;
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-weight: 600;
|
||||||
|
font-size: 15px;
|
||||||
|
|
||||||
|
small {
|
||||||
|
display: block;
|
||||||
|
color: $darker-text-color;
|
||||||
|
font-weight: 400;
|
||||||
|
font-size: 13px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-notes {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
background: $ui-base-color;
|
||||||
|
position: relative;
|
||||||
|
padding: 15px;
|
||||||
|
padding-left: 15px * 2 + 40px;
|
||||||
|
border-bottom: 1px solid darken($ui-base-color, 8%);
|
||||||
|
|
||||||
|
&:first-child {
|
||||||
|
border-top-left-radius: 4px;
|
||||||
|
border-top-right-radius: 4px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom-left-radius: 4px;
|
||||||
|
border-bottom-right-radius: 4px;
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:hover {
|
||||||
|
background-color: lighten($ui-base-color, 4%);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__avatar {
|
||||||
|
position: absolute;
|
||||||
|
left: 15px;
|
||||||
|
top: 15px;
|
||||||
|
border-radius: 4px;
|
||||||
|
width: 40px;
|
||||||
|
height: 40px;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__header {
|
||||||
|
color: $darker-text-color;
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
margin-bottom: 4px;
|
||||||
|
|
||||||
|
.username a {
|
||||||
|
color: $primary-text-color;
|
||||||
|
font-weight: 500;
|
||||||
|
text-decoration: none;
|
||||||
|
margin-right: 5px;
|
||||||
|
|
||||||
|
&:hover,
|
||||||
|
&:focus,
|
||||||
|
&:active {
|
||||||
|
text-decoration: underline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
time {
|
||||||
|
margin-left: 5px;
|
||||||
|
vertical-align: baseline;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__content {
|
||||||
|
font-size: 15px;
|
||||||
|
line-height: 20px;
|
||||||
|
word-wrap: break-word;
|
||||||
|
font-weight: 400;
|
||||||
|
color: $primary-text-color;
|
||||||
|
|
||||||
|
p {
|
||||||
|
margin-bottom: 20px;
|
||||||
|
white-space: pre-wrap;
|
||||||
|
unicode-bidi: plaintext;
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
margin-bottom: 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__actions {
|
||||||
|
position: absolute;
|
||||||
|
top: 15px;
|
||||||
|
right: 15px;
|
||||||
|
text-align: right;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.report-actions {
|
||||||
|
border: 1px solid darken($ui-base-color, 8%);
|
||||||
|
|
||||||
|
&__item {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
line-height: 18px;
|
||||||
|
border-bottom: 1px solid darken($ui-base-color, 8%);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__button {
|
||||||
|
flex: 0 0 auto;
|
||||||
|
width: 100px;
|
||||||
|
padding: 15px;
|
||||||
|
padding-right: 0;
|
||||||
|
|
||||||
|
.button {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
&__description {
|
||||||
|
padding: 15px;
|
||||||
|
font-size: 14px;
|
||||||
|
color: $dark-text-color;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -143,6 +143,21 @@
|
||||||
&:active {
|
&:active {
|
||||||
outline: 0 !important;
|
outline: 0 !important;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
&.disabled {
|
||||||
|
border-color: $dark-text-color;
|
||||||
|
|
||||||
|
&.active {
|
||||||
|
background: $dark-text-color;
|
||||||
|
}
|
||||||
|
|
||||||
|
&:active,
|
||||||
|
&:focus,
|
||||||
|
&:hover {
|
||||||
|
border-color: $dark-text-color;
|
||||||
|
border-width: 1px;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
&__number {
|
&__number {
|
||||||
|
|
|
@ -6,11 +6,11 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
|
||||||
end
|
end
|
||||||
|
|
||||||
def total
|
def total
|
||||||
Report.resolved.where(updated_at: time_period).count
|
Report.resolved.where(action_taken_at: time_period).count
|
||||||
end
|
end
|
||||||
|
|
||||||
def previous_total
|
def previous_total
|
||||||
Report.resolved.where(updated_at: previous_time_period).count
|
Report.resolved.where(action_taken_at: previous_time_period).count
|
||||||
end
|
end
|
||||||
|
|
||||||
def data
|
def data
|
||||||
|
@ -19,8 +19,7 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
|
||||||
WITH resolved_reports AS (
|
WITH resolved_reports AS (
|
||||||
SELECT reports.id
|
SELECT reports.id
|
||||||
FROM reports
|
FROM reports
|
||||||
WHERE action_taken
|
WHERE date_trunc('day', reports.action_taken_at)::date = axis.period
|
||||||
AND date_trunc('day', reports.updated_at)::date = axis.period
|
|
||||||
)
|
)
|
||||||
SELECT count(*) FROM resolved_reports
|
SELECT count(*) FROM resolved_reports
|
||||||
) AS value
|
) AS value
|
||||||
|
|
|
@ -160,11 +160,11 @@ class UserMailer < Devise::Mailer
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def warning(user, warning, status_ids = nil)
|
def warning(user, warning)
|
||||||
@resource = user
|
@resource = user
|
||||||
@warning = warning
|
@warning = warning
|
||||||
@instance = Rails.configuration.x.local_domain
|
@instance = Rails.configuration.x.local_domain
|
||||||
@statuses = Status.where(id: status_ids).includes(:account) if status_ids.is_a?(Array)
|
@statuses = @warning.statuses.includes(:account, :preloadable_poll, :media_attachments, active_mentions: [:account])
|
||||||
|
|
||||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||||
mail to: @resource.email,
|
mail to: @resource.email,
|
||||||
|
|
|
@ -10,14 +10,30 @@
|
||||||
# text :text default(""), not null
|
# text :text default(""), not null
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
|
# report_id :bigint(8)
|
||||||
|
# status_ids :string is an Array
|
||||||
#
|
#
|
||||||
|
|
||||||
class AccountWarning < ApplicationRecord
|
class AccountWarning < ApplicationRecord
|
||||||
enum action: %i(none disable sensitive silence suspend), _suffix: :action
|
enum action: {
|
||||||
|
none: 0,
|
||||||
|
disable: 1_000,
|
||||||
|
delete_statuses: 1_500,
|
||||||
|
sensitive: 2_000,
|
||||||
|
silence: 3_000,
|
||||||
|
suspend: 4_000,
|
||||||
|
}, _suffix: :action
|
||||||
|
|
||||||
belongs_to :account, inverse_of: :account_warnings
|
belongs_to :account, inverse_of: :account_warnings
|
||||||
belongs_to :target_account, class_name: 'Account', inverse_of: :targeted_account_warnings
|
belongs_to :target_account, class_name: 'Account', inverse_of: :strikes
|
||||||
|
belongs_to :report, optional: true
|
||||||
|
|
||||||
scope :latest, -> { order(created_at: :desc) }
|
has_one :appeal, dependent: :destroy
|
||||||
|
|
||||||
|
scope :latest, -> { order(id: :desc) }
|
||||||
scope :custom, -> { where.not(text: '') }
|
scope :custom, -> { where.not(text: '') }
|
||||||
|
|
||||||
|
def statuses
|
||||||
|
Status.with_discarded.where(id: status_ids || [])
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -33,7 +33,7 @@ class Admin::AccountAction
|
||||||
def save!
|
def save!
|
||||||
ApplicationRecord.transaction do
|
ApplicationRecord.transaction do
|
||||||
process_action!
|
process_action!
|
||||||
process_warning!
|
process_strike!
|
||||||
end
|
end
|
||||||
|
|
||||||
process_email!
|
process_email!
|
||||||
|
@ -74,20 +74,14 @@ class Admin::AccountAction
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_warning!
|
def process_strike!
|
||||||
return unless warnable?
|
@warning = target_account.strikes.create!(
|
||||||
|
|
||||||
authorize(target_account, :warn?)
|
|
||||||
|
|
||||||
@warning = AccountWarning.create!(target_account: target_account,
|
|
||||||
account: current_account,
|
account: current_account,
|
||||||
|
report: report,
|
||||||
action: type,
|
action: type,
|
||||||
text: text_for_warning)
|
text: text_for_warning,
|
||||||
|
status_ids: status_ids
|
||||||
# A log entry is only interesting if the warning contains
|
)
|
||||||
# custom text from someone. Otherwise it's just noise.
|
|
||||||
|
|
||||||
log_action(:create, warning) if warning.text.present?
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_reports!
|
def process_reports!
|
||||||
|
@ -143,7 +137,7 @@ class Admin::AccountAction
|
||||||
end
|
end
|
||||||
|
|
||||||
def process_email!
|
def process_email!
|
||||||
UserMailer.warning(target_account.user, warning, status_ids).deliver_later! if warnable?
|
UserMailer.warning(target_account.user, warning).deliver_later! if warnable?
|
||||||
end
|
end
|
||||||
|
|
||||||
def warnable?
|
def warnable?
|
||||||
|
@ -151,7 +145,7 @@ class Admin::AccountAction
|
||||||
end
|
end
|
||||||
|
|
||||||
def status_ids
|
def status_ids
|
||||||
report.status_ids if report && include_statuses
|
report.status_ids if with_report? && include_statuses
|
||||||
end
|
end
|
||||||
|
|
||||||
def reports
|
def reports
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::StatusBatchAction
|
||||||
|
include ActiveModel::Model
|
||||||
|
include AccountableConcern
|
||||||
|
include Authorization
|
||||||
|
|
||||||
|
attr_accessor :current_account, :type,
|
||||||
|
:status_ids, :report_id
|
||||||
|
|
||||||
|
def save!
|
||||||
|
process_action!
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def statuses
|
||||||
|
Status.with_discarded.where(id: status_ids)
|
||||||
|
end
|
||||||
|
|
||||||
|
def process_action!
|
||||||
|
return if status_ids.empty?
|
||||||
|
|
||||||
|
case type
|
||||||
|
when 'delete'
|
||||||
|
handle_delete!
|
||||||
|
when 'report'
|
||||||
|
handle_report!
|
||||||
|
when 'remove_from_report'
|
||||||
|
handle_remove_from_report!
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_delete!
|
||||||
|
statuses.each { |status| authorize(status, :destroy?) }
|
||||||
|
|
||||||
|
ApplicationRecord.transaction do
|
||||||
|
statuses.each do |status|
|
||||||
|
status.discard
|
||||||
|
log_action(:destroy, status)
|
||||||
|
end
|
||||||
|
|
||||||
|
if with_report?
|
||||||
|
report.resolve!(current_account)
|
||||||
|
log_action(:resolve, report)
|
||||||
|
end
|
||||||
|
|
||||||
|
@warning = target_account.strikes.create!(
|
||||||
|
action: :delete_statuses,
|
||||||
|
account: current_account,
|
||||||
|
report: report,
|
||||||
|
status_ids: status_ids
|
||||||
|
)
|
||||||
|
|
||||||
|
statuses.each { |status| Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true) } unless target_account.local?
|
||||||
|
end
|
||||||
|
|
||||||
|
UserMailer.warning(target_account.user, @warning).deliver_later! if target_account.local?
|
||||||
|
RemovalWorker.push_bulk(status_ids) { |status_id| [status_id, preserve: target_account.local?, immediate: !target_account.local?] }
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_report!
|
||||||
|
@report = Report.new(report_params) unless with_report?
|
||||||
|
@report.status_ids = (@report.status_ids + status_ids.map(&:to_i)).uniq
|
||||||
|
@report.save!
|
||||||
|
|
||||||
|
@report_id = @report.id
|
||||||
|
end
|
||||||
|
|
||||||
|
def handle_remove_from_report!
|
||||||
|
return unless with_report?
|
||||||
|
|
||||||
|
report.status_ids -= status_ids.map(&:to_i)
|
||||||
|
report.save!
|
||||||
|
end
|
||||||
|
|
||||||
|
def report
|
||||||
|
@report ||= Report.find(report_id) if report_id.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
def with_report?
|
||||||
|
!report.nil?
|
||||||
|
end
|
||||||
|
|
||||||
|
def target_account
|
||||||
|
@target_account ||= statuses.first.account
|
||||||
|
end
|
||||||
|
|
||||||
|
def report_params
|
||||||
|
{ account: current_account, target_account: target_account }
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,41 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class Admin::StatusFilter
|
||||||
|
KEYS = %i(
|
||||||
|
media
|
||||||
|
id
|
||||||
|
report_id
|
||||||
|
).freeze
|
||||||
|
|
||||||
|
attr_reader :params
|
||||||
|
|
||||||
|
def initialize(account, params)
|
||||||
|
@account = account
|
||||||
|
@params = params
|
||||||
|
end
|
||||||
|
|
||||||
|
def results
|
||||||
|
scope = @account.statuses.where(visibility: [:public, :unlisted])
|
||||||
|
|
||||||
|
params.each do |key, value|
|
||||||
|
next if %w(page report_id).include?(key.to_s)
|
||||||
|
|
||||||
|
scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
scope
|
||||||
|
end
|
||||||
|
|
||||||
|
private
|
||||||
|
|
||||||
|
def scope_for(key, value)
|
||||||
|
case key.to_s
|
||||||
|
when 'media'
|
||||||
|
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
|
||||||
|
when 'id'
|
||||||
|
Status.where(id: value)
|
||||||
|
else
|
||||||
|
raise "Unknown filter: #{key}"
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -42,7 +42,7 @@ module AccountAssociations
|
||||||
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
|
has_many :account_moderation_notes, dependent: :destroy, inverse_of: :account
|
||||||
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
has_many :targeted_moderation_notes, class_name: 'AccountModerationNote', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||||
has_many :account_warnings, dependent: :destroy, inverse_of: :account
|
has_many :account_warnings, dependent: :destroy, inverse_of: :account
|
||||||
has_many :targeted_account_warnings, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
has_many :strikes, class_name: 'AccountWarning', foreign_key: :target_account_id, dependent: :destroy, inverse_of: :target_account
|
||||||
|
|
||||||
# Lists (that the account is on, not owned by the account)
|
# Lists (that the account is on, not owned by the account)
|
||||||
has_many :list_accounts, inverse_of: :account, dependent: :destroy
|
has_many :list_accounts, inverse_of: :account, dependent: :destroy
|
||||||
|
|
|
@ -1,45 +0,0 @@
|
||||||
# frozen_string_literal: true
|
|
||||||
|
|
||||||
class Form::StatusBatch
|
|
||||||
include ActiveModel::Model
|
|
||||||
include AccountableConcern
|
|
||||||
|
|
||||||
attr_accessor :status_ids, :action, :current_account
|
|
||||||
|
|
||||||
def save
|
|
||||||
case action
|
|
||||||
when 'nsfw_on', 'nsfw_off'
|
|
||||||
change_sensitive(action == 'nsfw_on')
|
|
||||||
when 'delete'
|
|
||||||
delete_statuses
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
private
|
|
||||||
|
|
||||||
def change_sensitive(sensitive)
|
|
||||||
media_attached_status_ids = MediaAttachment.where(status_id: status_ids).pluck(:status_id)
|
|
||||||
|
|
||||||
ApplicationRecord.transaction do
|
|
||||||
Status.where(id: media_attached_status_ids).reorder(nil).find_each do |status|
|
|
||||||
status.update!(sensitive: sensitive)
|
|
||||||
log_action :update, status
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
|
||||||
rescue ActiveRecord::RecordInvalid
|
|
||||||
false
|
|
||||||
end
|
|
||||||
|
|
||||||
def delete_statuses
|
|
||||||
Status.where(id: status_ids).reorder(nil).find_each do |status|
|
|
||||||
status.discard
|
|
||||||
RemovalWorker.perform_async(status.id, immediate: true)
|
|
||||||
Tombstone.find_or_create_by(uri: status.uri, account: status.account, by_moderator: true)
|
|
||||||
log_action :destroy, status
|
|
||||||
end
|
|
||||||
|
|
||||||
true
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -6,7 +6,6 @@
|
||||||
# id :bigint(8) not null, primary key
|
# id :bigint(8) not null, primary key
|
||||||
# status_ids :bigint(8) default([]), not null, is an Array
|
# status_ids :bigint(8) default([]), not null, is an Array
|
||||||
# comment :text default(""), not null
|
# comment :text default(""), not null
|
||||||
# action_taken :boolean default(FALSE), not null
|
|
||||||
# created_at :datetime not null
|
# created_at :datetime not null
|
||||||
# updated_at :datetime not null
|
# updated_at :datetime not null
|
||||||
# account_id :bigint(8) not null
|
# account_id :bigint(8) not null
|
||||||
|
@ -15,9 +14,14 @@
|
||||||
# assigned_account_id :bigint(8)
|
# assigned_account_id :bigint(8)
|
||||||
# uri :string
|
# uri :string
|
||||||
# forwarded :boolean
|
# forwarded :boolean
|
||||||
|
# category :integer default("other"), not null
|
||||||
|
# action_taken_at :datetime
|
||||||
|
# rule_ids :bigint(8) is an Array
|
||||||
#
|
#
|
||||||
|
|
||||||
class Report < ApplicationRecord
|
class Report < ApplicationRecord
|
||||||
|
self.ignored_columns = %w(action_taken)
|
||||||
|
|
||||||
include Paginable
|
include Paginable
|
||||||
include RateLimitable
|
include RateLimitable
|
||||||
|
|
||||||
|
@ -30,11 +34,17 @@ class Report < ApplicationRecord
|
||||||
|
|
||||||
has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy
|
has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy
|
||||||
|
|
||||||
scope :unresolved, -> { where(action_taken: false) }
|
scope :unresolved, -> { where(action_taken_at: nil) }
|
||||||
scope :resolved, -> { where(action_taken: true) }
|
scope :resolved, -> { where.not(action_taken_at: nil) }
|
||||||
scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) }
|
scope :with_accounts, -> { includes([:account, :target_account, :action_taken_by_account, :assigned_account].index_with({ user: [:invite_request, :invite] })) }
|
||||||
|
|
||||||
validates :comment, length: { maximum: 1000 }
|
validates :comment, length: { maximum: 1_000 }
|
||||||
|
|
||||||
|
enum category: {
|
||||||
|
other: 0,
|
||||||
|
spam: 1_000,
|
||||||
|
violation: 2_000,
|
||||||
|
}
|
||||||
|
|
||||||
def local?
|
def local?
|
||||||
false # Force uri_for to use uri attribute
|
false # Force uri_for to use uri attribute
|
||||||
|
@ -47,13 +57,17 @@ class Report < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def statuses
|
def statuses
|
||||||
Status.with_discarded.where(id: status_ids).includes(:account, :media_attachments, :mentions)
|
Status.with_discarded.where(id: status_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
def media_attachments
|
def media_attachments
|
||||||
MediaAttachment.where(status_id: status_ids)
|
MediaAttachment.where(status_id: status_ids)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def rules
|
||||||
|
Rule.with_discarded.where(id: rule_ids)
|
||||||
|
end
|
||||||
|
|
||||||
def assign_to_self!(current_account)
|
def assign_to_self!(current_account)
|
||||||
update!(assigned_account_id: current_account.id)
|
update!(assigned_account_id: current_account.id)
|
||||||
end
|
end
|
||||||
|
@ -63,22 +77,19 @@ class Report < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def resolve!(acting_account)
|
def resolve!(acting_account)
|
||||||
if account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
|
update!(action_taken_at: Time.now.utc, action_taken_by_account_id: acting_account.id)
|
||||||
# This is an automated report and it is being dismissed, so it's
|
|
||||||
# a false positive, in which case update the account's trust level
|
|
||||||
# to prevent further spam checks
|
|
||||||
|
|
||||||
target_account.update(trust_level: Account::TRUST_LEVELS[:trusted])
|
|
||||||
end
|
|
||||||
|
|
||||||
RemovalWorker.push_bulk(Status.with_discarded.discarded.where(id: status_ids).pluck(:id)) { |status_id| [status_id, { immediate: true }] }
|
|
||||||
update!(action_taken: true, action_taken_by_account_id: acting_account.id)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
def unresolve!
|
def unresolve!
|
||||||
update!(action_taken: false, action_taken_by_account_id: nil)
|
update!(action_taken_at: nil, action_taken_by_account_id: nil)
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def action_taken?
|
||||||
|
action_taken_at.present?
|
||||||
|
end
|
||||||
|
|
||||||
|
alias action_taken action_taken?
|
||||||
|
|
||||||
def unresolved?
|
def unresolved?
|
||||||
!action_taken?
|
!action_taken?
|
||||||
end
|
end
|
||||||
|
@ -88,29 +99,24 @@ class Report < ApplicationRecord
|
||||||
end
|
end
|
||||||
|
|
||||||
def history
|
def history
|
||||||
time_range = created_at..updated_at
|
subquery = [
|
||||||
|
|
||||||
sql = [
|
|
||||||
Admin::ActionLog.where(
|
Admin::ActionLog.where(
|
||||||
target_type: 'Report',
|
target_type: 'Report',
|
||||||
target_id: id,
|
target_id: id
|
||||||
created_at: time_range
|
).unscope(:order).arel,
|
||||||
).unscope(:order),
|
|
||||||
|
|
||||||
Admin::ActionLog.where(
|
Admin::ActionLog.where(
|
||||||
target_type: 'Account',
|
target_type: 'Account',
|
||||||
target_id: target_account_id,
|
target_id: target_account_id
|
||||||
created_at: time_range
|
).unscope(:order).arel,
|
||||||
).unscope(:order),
|
|
||||||
|
|
||||||
Admin::ActionLog.where(
|
Admin::ActionLog.where(
|
||||||
target_type: 'Status',
|
target_type: 'Status',
|
||||||
target_id: status_ids,
|
target_id: status_ids
|
||||||
created_at: time_range
|
).unscope(:order).arel,
|
||||||
).unscope(:order),
|
].reduce { |union, query| Arel::Nodes::UnionAll.new(union, query) }
|
||||||
].map { |query| "(#{query.to_sql})" }.join(' UNION ALL ')
|
|
||||||
|
|
||||||
Admin::ActionLog.from("(#{sql}) AS admin_action_logs")
|
Admin::ActionLog.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table))
|
||||||
end
|
end
|
||||||
|
|
||||||
def set_uri
|
def set_uri
|
||||||
|
|
|
@ -19,7 +19,7 @@ class ReportFilter
|
||||||
scope = Report.unresolved
|
scope = Report.unresolved
|
||||||
|
|
||||||
params.each do |key, value|
|
params.each do |key, value|
|
||||||
scope = scope.merge scope_for(key, value)
|
scope = scope.merge scope_for(key, value), rewhere: true
|
||||||
end
|
end
|
||||||
|
|
||||||
scope
|
scope
|
||||||
|
|
|
@ -1,7 +1,7 @@
|
||||||
# frozen_string_literal: true
|
# frozen_string_literal: true
|
||||||
|
|
||||||
class REST::Admin::ReportSerializer < ActiveModel::Serializer
|
class REST::Admin::ReportSerializer < ActiveModel::Serializer
|
||||||
attributes :id, :action_taken, :comment, :created_at, :updated_at
|
attributes :id, :action_taken, :category, :comment, :created_at, :updated_at
|
||||||
|
|
||||||
has_one :account, serializer: REST::Admin::AccountSerializer
|
has_one :account, serializer: REST::Admin::AccountSerializer
|
||||||
has_one :target_account, serializer: REST::Admin::AccountSerializer
|
has_one :target_account, serializer: REST::Admin::AccountSerializer
|
||||||
|
@ -9,8 +9,13 @@ class REST::Admin::ReportSerializer < ActiveModel::Serializer
|
||||||
has_one :action_taken_by_account, serializer: REST::Admin::AccountSerializer
|
has_one :action_taken_by_account, serializer: REST::Admin::AccountSerializer
|
||||||
|
|
||||||
has_many :statuses, serializer: REST::StatusSerializer
|
has_many :statuses, serializer: REST::StatusSerializer
|
||||||
|
has_many :rules, serializer: REST::RuleSerializer
|
||||||
|
|
||||||
def id
|
def id
|
||||||
object.id.to_s
|
object.id.to_s
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def statuses
|
||||||
|
object.statuses.with_includes
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -9,6 +9,7 @@ class RemoveStatusService < BaseService
|
||||||
# @param [Hash] options
|
# @param [Hash] options
|
||||||
# @option [Boolean] :redraft
|
# @option [Boolean] :redraft
|
||||||
# @option [Boolean] :immediate
|
# @option [Boolean] :immediate
|
||||||
|
# @option [Boolean] :preserve
|
||||||
# @option [Boolean] :original_removed
|
# @option [Boolean] :original_removed
|
||||||
def call(status, **options)
|
def call(status, **options)
|
||||||
@payload = Oj.dump(event: :delete, payload: status.id.to_s)
|
@payload = Oj.dump(event: :delete, payload: status.id.to_s)
|
||||||
|
@ -44,7 +45,7 @@ class RemoveStatusService < BaseService
|
||||||
remove_media
|
remove_media
|
||||||
end
|
end
|
||||||
|
|
||||||
@status.destroy! if @options[:immediate] || !@status.reported?
|
@status.destroy! if permanently?
|
||||||
else
|
else
|
||||||
raise Mastodon::RaceConditionError
|
raise Mastodon::RaceConditionError
|
||||||
end
|
end
|
||||||
|
@ -143,11 +144,15 @@ class RemoveStatusService < BaseService
|
||||||
end
|
end
|
||||||
|
|
||||||
def remove_media
|
def remove_media
|
||||||
return if @options[:redraft] || (!@options[:immediate] && @status.reported?)
|
return if @options[:redraft] || !permanently?
|
||||||
|
|
||||||
@status.media_attachments.destroy_all
|
@status.media_attachments.destroy_all
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def permanently?
|
||||||
|
@options[:immediate] || !(@options[:preserve] || @status.reported?)
|
||||||
|
end
|
||||||
|
|
||||||
def lock_options
|
def lock_options
|
||||||
{ redis: Redis.current, key: "distribute:#{@status.id}", autorelease: 5.minutes.seconds }
|
{ redis: Redis.current, key: "distribute:#{@status.id}", autorelease: 5.minutes.seconds }
|
||||||
end
|
end
|
||||||
|
|
|
@ -19,7 +19,7 @@
|
||||||
%div.muted-hint.center-text
|
%div.muted-hint.center-text
|
||||||
= t 'admin.action_logs.empty'
|
= t 'admin.action_logs.empty'
|
||||||
- else
|
- else
|
||||||
.announcements-list
|
.report-notes
|
||||||
= render partial: 'action_log', collection: @action_logs
|
= render partial: 'action_log', collection: @action_logs
|
||||||
|
|
||||||
= paginate @action_logs
|
= paginate @action_logs
|
||||||
|
|
|
@ -1,7 +1,18 @@
|
||||||
.speech-bubble
|
.report-notes__item
|
||||||
.speech-bubble__bubble
|
= image_tag report_note.account.avatar.url, class: 'report-notes__item__avatar'
|
||||||
|
|
||||||
|
.report-notes__item__header
|
||||||
|
%span.username
|
||||||
|
= link_to display_name(report_note.account), admin_account_path(report_note.account_id)
|
||||||
|
%time{ datetime: report_note.created_at.iso8601, title: l(report_note.created_at) }
|
||||||
|
- if report_note.created_at.today?
|
||||||
|
= t('admin.report_notes.today_at', time: l(report_note.created_at, format: :time))
|
||||||
|
- else
|
||||||
|
= l report_note.created_at.to_date
|
||||||
|
|
||||||
|
.report-notes__item__content
|
||||||
= simple_format(h(report_note.content))
|
= simple_format(h(report_note.content))
|
||||||
.speech-bubble__owner
|
|
||||||
= admin_account_link_to report_note.account
|
- if can?(:destroy, report_note)
|
||||||
%time.formatted{ datetime: report_note.created_at.iso8601 }= l report_note.created_at
|
.report-notes__item__actions
|
||||||
= table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note)
|
= table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete
|
||||||
|
|
|
@ -1,6 +0,0 @@
|
||||||
.speech-bubble.positive
|
|
||||||
.speech-bubble__bubble
|
|
||||||
= t("admin.action_logs.actions.#{action_log.action}_#{action_log.target_type.underscore}_html", name: content_tag(:span, action_log.account.username, class: 'username'), target: content_tag(:span, log_target(action_log), class: 'target'))
|
|
||||||
.speech-bubble__owner
|
|
||||||
= admin_account_link_to(action_log.account)
|
|
||||||
%time.formatted{ datetime: action_log.created_at.iso8601 }= l action_log.created_at
|
|
|
@ -22,6 +22,9 @@
|
||||||
= react_component :media_gallery, height: 343, sensitive: status.proper.sensitive?, visible: false, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
= react_component :media_gallery, height: 343, sensitive: status.proper.sensitive?, visible: false, media: status.proper.media_attachments.map { |a| ActiveModelSerializers::SerializableResource.new(a, serializer: REST::MediaAttachmentSerializer).as_json }
|
||||||
|
|
||||||
.detailed-status__meta
|
.detailed-status__meta
|
||||||
|
- if status.application
|
||||||
|
= status.application.name
|
||||||
|
·
|
||||||
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
|
= link_to ActivityPub::TagManager.instance.url_for(status), class: 'detailed-status__datetime', target: stream_link_target, rel: 'noopener noreferrer' do
|
||||||
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
|
||||||
- if status.discarded?
|
- if status.discarded?
|
||||||
|
|
|
@ -7,122 +7,199 @@
|
||||||
- else
|
- else
|
||||||
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
|
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
|
||||||
|
|
||||||
.table-wrapper
|
.report-header
|
||||||
%table.table.inline-table
|
.report-header__card
|
||||||
%tbody
|
.account-card
|
||||||
%tr
|
.account-card__header
|
||||||
%th= t('admin.reports.reported_account')
|
= image_tag @report.target_account.header.url, alt: ''
|
||||||
%td= admin_account_link_to @report.target_account
|
.account-card__title
|
||||||
%td= table_link_to 'flag', t('admin.reports.account.reports', count: @report.target_account.targeted_reports.count), admin_reports_path(target_account_id: @report.target_account.id)
|
.account-card__title__avatar
|
||||||
%td= table_link_to 'file', t('admin.reports.account.notes', count: @report.target_account.targeted_moderation_notes.count), admin_reports_path(target_account_id: @report.target_account.id)
|
= image_tag @report.target_account.avatar.url, alt: ''
|
||||||
%tr
|
.display-name
|
||||||
%th= t('admin.reports.reported_by')
|
%bdi
|
||||||
- if @report.account.instance_actor?
|
%strong.emojify.p-name= display_name(@report.target_account, custom_emojify: true)
|
||||||
%td{ colspan: 3 }= site_hostname
|
%span
|
||||||
- elsif @report.account.local?
|
= acct(@report.target_account)
|
||||||
%td= admin_account_link_to @report.account
|
= fa_icon('lock') if @report.target_account.locked?
|
||||||
%td= table_link_to 'flag', t('admin.reports.account.reports', count: @report.account.targeted_reports.count), admin_reports_path(target_account_id: @report.account.id)
|
- if @report.target_account.note.present?
|
||||||
%td= table_link_to 'file', t('admin.reports.account.notes', count: @report.account.targeted_moderation_notes.count), admin_reports_path(target_account_id: @report.account.id)
|
.account-card__bio.emojify
|
||||||
- else
|
= Formatter.instance.simplified_format(@report.target_account, custom_emojify: true)
|
||||||
%td{ colspan: 3 }= @report.account.domain
|
.account-card__actions
|
||||||
%tr
|
.account-card__counters
|
||||||
%th= t('admin.reports.created_at')
|
.account-card__counters__item
|
||||||
%td{ colspan: 3 }
|
= friendly_number_to_human @report.target_account.statuses_count
|
||||||
|
%small= t('accounts.posts', count: @report.target_account.statuses_count).downcase
|
||||||
|
.account-card__counters__item
|
||||||
|
= friendly_number_to_human @report.target_account.followers_count
|
||||||
|
%small= t('accounts.followers', count: @report.target_account.followers_count).downcase
|
||||||
|
.account-card__counters__item
|
||||||
|
= friendly_number_to_human @report.target_account.following_count
|
||||||
|
%small= t('accounts.following', count: @report.target_account.following_count).downcase
|
||||||
|
.account-card__actions__button
|
||||||
|
= link_to t('admin.reports.view_profile'), admin_account_path(@report.target_account_id), class: 'button'
|
||||||
|
.report-header__details.report-header__details--horizontal
|
||||||
|
.report-header__details__item
|
||||||
|
.report-header__details__item__header
|
||||||
|
%strong= t('admin.accounts.joined')
|
||||||
|
.report-header__details__item__content
|
||||||
|
%time.time-ago{ datetime: @report.target_account.created_at.iso8601, title: l(@report.target_account.created_at) }= l @report.target_account.created_at
|
||||||
|
.report-header__details__item
|
||||||
|
.report-header__details__item__header
|
||||||
|
%strong= t('accounts.last_active')
|
||||||
|
.report-header__details__item__content
|
||||||
|
- if @report.target_account.last_status_at.present?
|
||||||
|
%time.time-ago{ datetime: @report.target_account.last_status_at.to_date.iso8601, title: l(@report.target_account.last_status_at.to_date) }= l @report.target_account.last_status_at
|
||||||
|
.report-header__details__item
|
||||||
|
.report-header__details__item__header
|
||||||
|
%strong= t('admin.accounts.strikes')
|
||||||
|
.report-header__details__item__content
|
||||||
|
= @report.target_account.strikes.count
|
||||||
|
|
||||||
|
.report-header__details
|
||||||
|
.report-header__details__item
|
||||||
|
.report-header__details__item__header
|
||||||
|
%strong= t('admin.reports.created_at')
|
||||||
|
.report-header__details__item__content
|
||||||
%time.formatted{ datetime: @report.created_at.iso8601 }
|
%time.formatted{ datetime: @report.created_at.iso8601 }
|
||||||
%tr
|
.report-header__details__item
|
||||||
%th= t('admin.reports.updated_at')
|
.report-header__details__item__header
|
||||||
%td{ colspan: 3 }
|
%strong= t('admin.reports.reported_by')
|
||||||
%time.formatted{ datetime: @report.updated_at.iso8601 }
|
.report-header__details__item__content
|
||||||
%tr
|
- if @report.account.instance_actor?
|
||||||
%th= t('admin.reports.status')
|
= site_hostname
|
||||||
%td
|
- elsif @report.account.local?
|
||||||
|
= admin_account_link_to @report.account
|
||||||
|
- else
|
||||||
|
= @report.account.domain
|
||||||
|
.report-header__details__item
|
||||||
|
.report-header__details__item__header
|
||||||
|
%strong= t('admin.reports.status')
|
||||||
|
.report-header__details__item__content
|
||||||
- if @report.action_taken?
|
- if @report.action_taken?
|
||||||
= t('admin.reports.resolved')
|
= t('admin.reports.resolved')
|
||||||
- else
|
- else
|
||||||
= t('admin.reports.unresolved')
|
= t('admin.reports.unresolved')
|
||||||
%td{ colspan: 2 }
|
|
||||||
- if @report.action_taken?
|
|
||||||
= table_link_to 'envelope-open', t('admin.reports.reopen'), admin_report_path(@report, outcome: 'reopen'), method: :put
|
|
||||||
- unless @report.target_account.local?
|
- unless @report.target_account.local?
|
||||||
%tr
|
.report-header__details__item
|
||||||
%th= t('admin.reports.forwarded')
|
.report-header__details__item__header
|
||||||
%td{ colspan: 3 }
|
%strong= t('admin.reports.forwarded')
|
||||||
- if @report.forwarded.nil?
|
.report-header__details__item__content
|
||||||
\-
|
- if @report.forwarded?
|
||||||
- elsif @report.forwarded?
|
|
||||||
= t('simple_form.yes')
|
= t('simple_form.yes')
|
||||||
- else
|
- else
|
||||||
= t('simple_form.no')
|
= t('simple_form.no')
|
||||||
- if !@report.action_taken_by_account.nil?
|
- if !@report.action_taken_by_account.nil?
|
||||||
%tr
|
.report-header__details__item
|
||||||
%th= t('admin.reports.action_taken_by')
|
.report-header__details__item__header
|
||||||
%td{ colspan: 3 }
|
%strong= t('admin.reports.action_taken_by')
|
||||||
|
.report-header__details__item__content
|
||||||
= admin_account_link_to @report.action_taken_by_account
|
= admin_account_link_to @report.action_taken_by_account
|
||||||
- else
|
- else
|
||||||
%tr
|
.report-header__details__item
|
||||||
%th= t('admin.reports.assigned')
|
.report-header__details__item__header
|
||||||
%td
|
%strong= t('admin.reports.assigned')
|
||||||
|
.report-header__details__item__content
|
||||||
- if @report.assigned_account.nil?
|
- if @report.assigned_account.nil?
|
||||||
\-
|
= t 'admin.reports.no_one_assigned'
|
||||||
- else
|
- else
|
||||||
= admin_account_link_to @report.assigned_account
|
= admin_account_link_to @report.assigned_account
|
||||||
%td
|
—
|
||||||
- if @report.assigned_account != current_user.account
|
- if @report.assigned_account != current_user.account
|
||||||
= table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post
|
= table_link_to 'user', t('admin.reports.assign_to_self'), assign_to_self_admin_report_path(@report), method: :post
|
||||||
%td
|
- elsif !@report.assigned_account.nil?
|
||||||
- if !@report.assigned_account.nil?
|
|
||||||
= table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post
|
= table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post
|
||||||
|
|
||||||
%hr.spacer
|
%hr.spacer
|
||||||
|
|
||||||
%div.action-buttons
|
%h3= t 'admin.reports.category'
|
||||||
%div
|
|
||||||
|
|
||||||
- if @report.unresolved?
|
%p= t 'admin.reports.category_description_html'
|
||||||
%div
|
|
||||||
- if @report.target_account.local?
|
|
||||||
= link_to t('admin.accounts.warn'), new_admin_account_action_path(@report.target_account_id, type: 'none', report_id: @report.id), class: 'button'
|
|
||||||
= link_to t('admin.accounts.disable'), new_admin_account_action_path(@report.target_account_id, type: 'disable', report_id: @report.id), class: 'button button--destructive'
|
|
||||||
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive'
|
|
||||||
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, type: 'suspend', report_id: @report.id), class: 'button button--destructive'
|
|
||||||
|
|
||||||
%hr.spacer
|
= react_admin_component :report_reason_selector, id: @report.id, category: @report.category, rule_ids: @report.rule_ids&.map(&:to_s), disabled: @report.action_taken?
|
||||||
|
|
||||||
.speech-bubble
|
- if @report.comment.present?
|
||||||
.speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none'))
|
%p= t('admin.reports.comment_description_html', name: content_tag(:strong, @report.account.username, class: 'username'))
|
||||||
.speech-bubble__owner
|
|
||||||
- if @report.account.local?
|
.report-notes__item
|
||||||
= admin_account_link_to @report.account
|
= image_tag @report.account.avatar.url, class: 'report-notes__item__avatar'
|
||||||
|
|
||||||
|
.report-notes__item__header
|
||||||
|
%span.username
|
||||||
|
= link_to display_name(@report.account), admin_account_path(@report.account_id)
|
||||||
|
%time{ datetime: @report.created_at.iso8601, title: l(@report.created_at) }
|
||||||
|
- if @report.created_at.today?
|
||||||
|
= t('admin.report_notes.today_at', time: l(@report.created_at, format: :time))
|
||||||
- else
|
- else
|
||||||
= @report.account.domain
|
= l @report.created_at.to_date
|
||||||
%br/
|
|
||||||
%time.formatted{ datetime: @report.created_at.iso8601 }
|
.report-notes__item__content
|
||||||
|
= simple_format(h(@report.comment))
|
||||||
|
|
||||||
- unless @report.statuses.empty?
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
= form_for(@form, url: admin_report_reported_statuses_path(@report.id)) do |f|
|
%h3= t 'admin.reports.statuses'
|
||||||
|
|
||||||
|
%p
|
||||||
|
= t 'admin.reports.statuses_description_html'
|
||||||
|
—
|
||||||
|
= link_to safe_join([fa_icon('plus'), t('admin.reports.add_to_report')]), admin_account_statuses_path(@report.target_account_id, report_id: @report.id), class: 'table-action-link'
|
||||||
|
|
||||||
|
= form_for(@form, url: batch_admin_account_statuses_path(@report.target_account_id, report_id: @report.id)) do |f|
|
||||||
.batch-table
|
.batch-table
|
||||||
.batch-table__toolbar
|
.batch-table__toolbar
|
||||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||||
= check_box_tag :batch_checkbox_all, nil, false
|
= check_box_tag :batch_checkbox_all, nil, false
|
||||||
.batch-table__toolbar__actions
|
.batch-table__toolbar__actions
|
||||||
= f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
- if !@statuses.empty? && @report.unresolved?
|
||||||
= f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
= f.button safe_join([fa_icon('times'), t('admin.statuses.batch.remove_from_report')]), name: :remove_from_report, class: 'table-action-link', type: :submit
|
||||||
= f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
= f.button safe_join([fa_icon('trash'), t('admin.reports.delete_and_resolve')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||||
|
- else
|
||||||
.batch-table__body
|
.batch-table__body
|
||||||
= render partial: 'admin/reports/status', collection: @report.statuses, locals: { f: f }
|
- if @statuses.empty?
|
||||||
|
= nothing_here 'nothing-here--under-tabs'
|
||||||
|
- else
|
||||||
|
= render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
|
||||||
|
|
||||||
|
- if @report.unresolved?
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
|
%p= t 'admin.reports.actions_description_html'
|
||||||
|
|
||||||
|
.report-actions
|
||||||
|
.report-actions__item
|
||||||
|
.report-actions__item__button
|
||||||
|
= link_to t('admin.accounts.silence'), new_admin_account_action_path(@report.target_account_id, type: 'silence', report_id: @report.id), class: 'button button--destructive'
|
||||||
|
.report-actions__item__description
|
||||||
|
= t('admin.reports.actions.silence_description_html')
|
||||||
|
.report-actions__item
|
||||||
|
.report-actions__item__button
|
||||||
|
= link_to t('admin.accounts.perform_full_suspension'), new_admin_account_action_path(@report.target_account_id, report_id: @report.id, type: 'suspend'), class: 'button button--destructive'
|
||||||
|
.report-actions__item__description
|
||||||
|
= t('admin.reports.actions.suspend_description_html')
|
||||||
|
.report-actions__item
|
||||||
|
.report-actions__item__button
|
||||||
|
= link_to t('admin.accounts.custom'), new_admin_account_action_path(@report.target_account_id, report_id: @report.id), class: 'button'
|
||||||
|
.report-actions__item__description
|
||||||
|
= t('admin.reports.actions.other_description_html')
|
||||||
|
|
||||||
|
- unless @action_logs.empty?
|
||||||
|
%hr.spacer/
|
||||||
|
|
||||||
|
%h3= t 'admin.reports.action_log'
|
||||||
|
|
||||||
|
.report-notes
|
||||||
|
= render @action_logs
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
- @report_notes.each do |item|
|
%h3= t 'admin.reports.notes.title'
|
||||||
- if item.is_a?(Admin::ActionLog)
|
|
||||||
= render partial: 'action_log', locals: { action_log: item }
|
%p= t 'admin.reports.notes_description_html'
|
||||||
- else
|
|
||||||
= render item
|
.report-notes
|
||||||
|
= render @report_notes
|
||||||
|
|
||||||
= simple_form_for @report_note, url: admin_report_notes_path do |f|
|
= simple_form_for @report_note, url: admin_report_notes_path do |f|
|
||||||
= render 'shared/error_messages', object: @report_note
|
|
||||||
= f.input :report_id, as: :hidden
|
= f.input :report_id, as: :hidden
|
||||||
|
|
||||||
.field-group
|
.field-group
|
||||||
|
|
|
@ -7,28 +7,37 @@
|
||||||
.filter-subset
|
.filter-subset
|
||||||
%strong= t('admin.statuses.media.title')
|
%strong= t('admin.statuses.media.title')
|
||||||
%ul
|
%ul
|
||||||
%li= link_to t('admin.statuses.no_media'), admin_account_statuses_path(@account.id, current_params.merge(media: nil)), class: !params[:media] && 'selected'
|
%li= filter_link_to t('generic.all'), media: nil, id: nil
|
||||||
%li= link_to t('admin.statuses.with_media'), admin_account_statuses_path(@account.id, current_params.merge(media: true)), class: params[:media] && 'selected'
|
%li= filter_link_to t('admin.statuses.with_media'), media: '1'
|
||||||
.back-link
|
.back-link
|
||||||
|
- if params[:report_id]
|
||||||
|
= link_to admin_report_path(params[:report_id].to_i) do
|
||||||
|
= fa_icon 'chevron-left fw'
|
||||||
|
= t('admin.statuses.back_to_report')
|
||||||
|
- else
|
||||||
= link_to admin_account_path(@account.id) do
|
= link_to admin_account_path(@account.id) do
|
||||||
= fa_icon 'chevron-left fw'
|
= fa_icon 'chevron-left fw'
|
||||||
= t('admin.statuses.back_to_account')
|
= t('admin.statuses.back_to_account')
|
||||||
|
|
||||||
%hr.spacer/
|
%hr.spacer/
|
||||||
|
|
||||||
= form_for(@form, url: admin_account_statuses_path(@account.id)) do |f|
|
= form_for(@status_batch_action, url: batch_admin_account_statuses_path(@account.id)) do |f|
|
||||||
= hidden_field_tag :page, params[:page]
|
= hidden_field_tag :page, params[:page] || 1
|
||||||
= hidden_field_tag :media, params[:media]
|
|
||||||
|
- Admin::StatusFilter::KEYS.each do |key|
|
||||||
|
= hidden_field_tag key, params[key] if params[key].present?
|
||||||
|
|
||||||
.batch-table
|
.batch-table
|
||||||
.batch-table__toolbar
|
.batch-table__toolbar
|
||||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
%label.batch-table__toolbar__select.batch-checkbox-all
|
||||||
= check_box_tag :batch_checkbox_all, nil, false
|
= check_box_tag :batch_checkbox_all, nil, false
|
||||||
.batch-table__toolbar__actions
|
.batch-table__toolbar__actions
|
||||||
= f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
- unless @statuses.empty?
|
||||||
= f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
= f.button safe_join([fa_icon('flag'), t('admin.statuses.batch.report')]), name: :report, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
||||||
= f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
|
||||||
.batch-table__body
|
.batch-table__body
|
||||||
|
- if @statuses.empty?
|
||||||
|
= nothing_here 'nothing-here--under-tabs'
|
||||||
|
- else
|
||||||
= render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
|
= render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
|
||||||
|
|
||||||
= paginate @statuses
|
= paginate @statuses
|
||||||
|
|
|
@ -1,27 +0,0 @@
|
||||||
- content_for :page_title do
|
|
||||||
= t('admin.statuses.title')
|
|
||||||
\-
|
|
||||||
= "@#{@account.acct}"
|
|
||||||
|
|
||||||
.filters
|
|
||||||
.back-link
|
|
||||||
= link_to admin_account_path(@account.id) do
|
|
||||||
%i.fa.fa-chevron-left.fa-fw
|
|
||||||
= t('admin.statuses.back_to_account')
|
|
||||||
|
|
||||||
%hr.spacer/
|
|
||||||
|
|
||||||
= form_for(@form, url: admin_account_statuses_path(@account.id)) do |f|
|
|
||||||
= hidden_field_tag :page, params[:page]
|
|
||||||
= hidden_field_tag :media, params[:media]
|
|
||||||
|
|
||||||
.batch-table
|
|
||||||
.batch-table__toolbar
|
|
||||||
%label.batch-table__toolbar__select.batch-checkbox-all
|
|
||||||
= check_box_tag :batch_checkbox_all, nil, false
|
|
||||||
.batch-table__toolbar__actions
|
|
||||||
= f.button safe_join([fa_icon('eye-slash'), t('admin.statuses.batch.nsfw_on')]), name: :nsfw_on, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
|
||||||
= f.button safe_join([fa_icon('eye'), t('admin.statuses.batch.nsfw_off')]), name: :nsfw_off, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
|
||||||
= f.button safe_join([fa_icon('trash'), t('admin.statuses.batch.delete')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
|
|
||||||
.batch-table__body
|
|
||||||
= render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
|
|
|
@ -1,8 +1,8 @@
|
||||||
<% if status.spoiler_text? %>
|
<% if status.spoiler_text? %>
|
||||||
<%= raw status.spoiler_text %>
|
> <%= raw word_wrap(status.spoiler_text, break_sequence: "\n> ") %>
|
||||||
----
|
> ----
|
||||||
|
>
|
||||||
<% end %>
|
<% end %>
|
||||||
<%= raw Formatter.instance.plaintext(status) %>
|
> <%= raw word_wrap(Formatter.instance.plaintext(status), break_sequence: "\n> ") %>
|
||||||
|
|
||||||
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>
|
<%= raw t('application_mailer.view')%> <%= web_url("statuses/#{status.id}") %>
|
||||||
|
|
|
@ -37,16 +37,26 @@
|
||||||
%tr
|
%tr
|
||||||
%td.column-cell.text-center
|
%td.column-cell.text-center
|
||||||
- unless @warning.none_action?
|
- unless @warning.none_action?
|
||||||
%p= t "user_mailer.warning.explanation.#{@warning.action}"
|
%p= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance
|
||||||
|
|
||||||
- unless @warning.text.blank?
|
- unless @warning.text.blank?
|
||||||
= Formatter.instance.linkify(@warning.text)
|
= Formatter.instance.linkify(@warning.text)
|
||||||
|
|
||||||
- if !@statuses.nil? && !@statuses.empty?
|
- if @warning.report && !@warning.report.other?
|
||||||
|
%p
|
||||||
|
%strong= t('user_mailer.warning.reason')
|
||||||
|
= t("user_mailer.warning.categories.#{@warning.report.category}")
|
||||||
|
|
||||||
|
- if @warning.report.violation? && @warning.report.rule_ids.present?
|
||||||
|
%ul.rules-list
|
||||||
|
- @warning.report.rules.each do |rule|
|
||||||
|
%li= rule.text
|
||||||
|
|
||||||
|
- unless @statuses.empty?
|
||||||
%p
|
%p
|
||||||
%strong= t('user_mailer.warning.statuses')
|
%strong= t('user_mailer.warning.statuses')
|
||||||
|
|
||||||
- if !@statuses.nil? && !@statuses.empty?
|
- unless @statuses.empty?
|
||||||
- @statuses.each_with_index do |status, i|
|
- @statuses.each_with_index do |status, i|
|
||||||
= render 'notification_mailer/status', status: status, i: i + 1, highlighted: true
|
= render 'notification_mailer/status', status: status, i: i + 1, highlighted: true
|
||||||
|
|
||||||
|
|
|
@ -3,11 +3,24 @@
|
||||||
===
|
===
|
||||||
|
|
||||||
<% unless @warning.none_action? %>
|
<% unless @warning.none_action? %>
|
||||||
<%= t "user_mailer.warning.explanation.#{@warning.action}" %>
|
<%= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance %>
|
||||||
|
|
||||||
<% end %>
|
<% end %>
|
||||||
|
<% if @warning.text.present? %>
|
||||||
<%= @warning.text %>
|
<%= @warning.text %>
|
||||||
<% if !@statuses.nil? && !@statuses.empty? %>
|
|
||||||
|
<% end %>
|
||||||
|
<% if @warning.report && !@warning.report.other? %>
|
||||||
|
**<%= t('user_mailer.warning.reason') %>** <%= t("user_mailer.warning.categories.#{@warning.report.category}") %>
|
||||||
|
|
||||||
|
<% if @warning.report.violation? && @warning.report.rule_ids.present? %>
|
||||||
|
<% @warning.report.rules.each do |rule| %>
|
||||||
|
- <%= rule.text %>
|
||||||
|
<% end %>
|
||||||
|
|
||||||
|
<% end %>
|
||||||
|
<% end %>
|
||||||
|
<% if !@statuses.empty? %>
|
||||||
<%= t('user_mailer.warning.statuses') %>
|
<%= t('user_mailer.warning.statuses') %>
|
||||||
|
|
||||||
<% @statuses.each do |status| %>
|
<% @statuses.each do |status| %>
|
||||||
|
|
|
@ -8,6 +8,7 @@ class Scheduler::UserCleanupScheduler
|
||||||
def perform
|
def perform
|
||||||
clean_unconfirmed_accounts!
|
clean_unconfirmed_accounts!
|
||||||
clean_suspended_accounts!
|
clean_suspended_accounts!
|
||||||
|
clean_discarded_statuses!
|
||||||
end
|
end
|
||||||
|
|
||||||
private
|
private
|
||||||
|
@ -24,4 +25,12 @@ class Scheduler::UserCleanupScheduler
|
||||||
Admin::AccountDeletionWorker.perform_async(deletion_request.account_id)
|
Admin::AccountDeletionWorker.perform_async(deletion_request.account_id)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def clean_discarded_statuses!
|
||||||
|
Status.discarded.where('deleted_at <= ?', 30.days.ago).find_in_batches do |statuses|
|
||||||
|
RemovalWorker.push_bulk(statuses) do |status|
|
||||||
|
[status.id, { immediate: true }]
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -113,6 +113,7 @@ en:
|
||||||
confirm: Confirm
|
confirm: Confirm
|
||||||
confirmed: Confirmed
|
confirmed: Confirmed
|
||||||
confirming: Confirming
|
confirming: Confirming
|
||||||
|
custom: Custom
|
||||||
delete: Delete data
|
delete: Delete data
|
||||||
deleted: Deleted
|
deleted: Deleted
|
||||||
demote: Demote
|
demote: Demote
|
||||||
|
@ -203,6 +204,7 @@ en:
|
||||||
silence: Limit
|
silence: Limit
|
||||||
silenced: Limited
|
silenced: Limited
|
||||||
statuses: Posts
|
statuses: Posts
|
||||||
|
strikes: Previous strikes
|
||||||
subscribe: Subscribe
|
subscribe: Subscribe
|
||||||
suspended: Suspended
|
suspended: Suspended
|
||||||
suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had.
|
suspension_irreversible: The data of this account has been irreversibly deleted. You can unsuspend the account to make it usable but it will not recover any data it previously had.
|
||||||
|
@ -549,32 +551,44 @@ en:
|
||||||
report_notes:
|
report_notes:
|
||||||
created_msg: Report note successfully created!
|
created_msg: Report note successfully created!
|
||||||
destroyed_msg: Report note successfully deleted!
|
destroyed_msg: Report note successfully deleted!
|
||||||
|
today_at: Today at %{time}
|
||||||
reports:
|
reports:
|
||||||
account:
|
account:
|
||||||
notes:
|
notes:
|
||||||
one: "%{count} note"
|
one: "%{count} note"
|
||||||
other: "%{count} notes"
|
other: "%{count} notes"
|
||||||
reports:
|
action_log: Audit log
|
||||||
one: "%{count} report"
|
|
||||||
other: "%{count} reports"
|
|
||||||
action_taken_by: Action taken by
|
action_taken_by: Action taken by
|
||||||
|
actions:
|
||||||
|
other_description_html: See more options for controlling the account's behaviour and customize communication to the reported account.
|
||||||
|
silence_description_html: The profile will be visible only to those who already follow it or manually look it up, severely limiting its reach. Can always be reverted.
|
||||||
|
suspend_description_html: The profile and all its contents will become inaccessible until it is eventually deleted. Interacting with the account will be impossible. Reversible within 30 days.
|
||||||
|
actions_description_html: 'If removing the offending content above is insufficient:'
|
||||||
|
add_to_report: Add more to report
|
||||||
are_you_sure: Are you sure?
|
are_you_sure: Are you sure?
|
||||||
assign_to_self: Assign to me
|
assign_to_self: Assign to me
|
||||||
assigned: Assigned moderator
|
assigned: Assigned moderator
|
||||||
by_target_domain: Domain of reported account
|
by_target_domain: Domain of reported account
|
||||||
|
category: Category
|
||||||
|
category_description_html: The reason this account and/or content was reported will be cited in communication with the reported account
|
||||||
comment:
|
comment:
|
||||||
none: None
|
none: None
|
||||||
|
comment_description_html: 'To provide more information, %{name} wrote:'
|
||||||
created_at: Reported
|
created_at: Reported
|
||||||
|
delete_and_resolve: Delete and resolve
|
||||||
forwarded: Forwarded
|
forwarded: Forwarded
|
||||||
forwarded_to: Forwarded to %{domain}
|
forwarded_to: Forwarded to %{domain}
|
||||||
mark_as_resolved: Mark as resolved
|
mark_as_resolved: Mark as resolved
|
||||||
mark_as_unresolved: Mark as unresolved
|
mark_as_unresolved: Mark as unresolved
|
||||||
|
no_one_assigned: No one
|
||||||
notes:
|
notes:
|
||||||
create: Add note
|
create: Add note
|
||||||
create_and_resolve: Resolve with note
|
create_and_resolve: Resolve with note
|
||||||
create_and_unresolve: Reopen with note
|
create_and_unresolve: Reopen with note
|
||||||
delete: Delete
|
delete: Delete
|
||||||
placeholder: Describe what actions have been taken, or any other related updates...
|
placeholder: Describe what actions have been taken, or any other related updates...
|
||||||
|
title: Notes
|
||||||
|
notes_description_html: View and leave notes to other moderators and your future self
|
||||||
reopen: Reopen report
|
reopen: Reopen report
|
||||||
report: 'Report #%{id}'
|
report: 'Report #%{id}'
|
||||||
reported_account: Reported account
|
reported_account: Reported account
|
||||||
|
@ -582,11 +596,14 @@ en:
|
||||||
resolved: Resolved
|
resolved: Resolved
|
||||||
resolved_msg: Report successfully resolved!
|
resolved_msg: Report successfully resolved!
|
||||||
status: Status
|
status: Status
|
||||||
|
statuses: Reported content
|
||||||
|
statuses_description_html: Offending content will be cited in communication with the reported account
|
||||||
target_origin: Origin of reported account
|
target_origin: Origin of reported account
|
||||||
title: Reports
|
title: Reports
|
||||||
unassign: Unassign
|
unassign: Unassign
|
||||||
unresolved: Unresolved
|
unresolved: Unresolved
|
||||||
updated_at: Updated
|
updated_at: Updated
|
||||||
|
view_profile: View profile
|
||||||
rules:
|
rules:
|
||||||
add_new: Add rule
|
add_new: Add rule
|
||||||
delete: Delete
|
delete: Delete
|
||||||
|
@ -688,15 +705,13 @@ en:
|
||||||
destroyed_msg: Site upload successfully deleted!
|
destroyed_msg: Site upload successfully deleted!
|
||||||
statuses:
|
statuses:
|
||||||
back_to_account: Back to account page
|
back_to_account: Back to account page
|
||||||
|
back_to_report: Back to report page
|
||||||
batch:
|
batch:
|
||||||
delete: Delete
|
remove_from_report: Remove from report
|
||||||
nsfw_off: Mark as not sensitive
|
report: Report
|
||||||
nsfw_on: Mark as sensitive
|
|
||||||
deleted: Deleted
|
deleted: Deleted
|
||||||
failed_to_execute: Failed to execute
|
|
||||||
media:
|
media:
|
||||||
title: Media
|
title: Media
|
||||||
no_media: No media
|
|
||||||
no_status_selected: No posts were changed as none were selected
|
no_status_selected: No posts were changed as none were selected
|
||||||
title: Account posts
|
title: Account posts
|
||||||
with_media: With media
|
with_media: With media
|
||||||
|
@ -1457,6 +1472,7 @@ en:
|
||||||
formats:
|
formats:
|
||||||
default: "%b %d, %Y, %H:%M"
|
default: "%b %d, %Y, %H:%M"
|
||||||
month: "%b %Y"
|
month: "%b %Y"
|
||||||
|
time: "%H:%M"
|
||||||
two_factor_authentication:
|
two_factor_authentication:
|
||||||
add: Add
|
add: Add
|
||||||
disable: Disable 2FA
|
disable: Disable 2FA
|
||||||
|
@ -1484,24 +1500,31 @@ en:
|
||||||
subject: Please confirm attempted sign in
|
subject: Please confirm attempted sign in
|
||||||
title: Sign in attempt
|
title: Sign in attempt
|
||||||
warning:
|
warning:
|
||||||
|
categories:
|
||||||
|
spam: Spam
|
||||||
|
violation: Content violates the following community guidelines
|
||||||
explanation:
|
explanation:
|
||||||
disable: You can no longer login to your account or use it in any other way, but your profile and other data remains intact.
|
delete_statuses: Some of your posts have been found to violate one or more community guidelines and have been subsequently removed by the moderators of %{instance}. Future violations may result in harsher punitive actions against your account.
|
||||||
sensitive: Your uploaded media files and linked media will be treated as sensitive.
|
disable: You can no longer use your account, but your profile and other data remains intact. You can request a backup of your data, change account settings or delete your account.
|
||||||
silence: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various public listings. However, others may still manually follow you.
|
sensitive: From now on, all your uploaded media files will be marked as sensitive and hidden behind a click-through warning.
|
||||||
suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed, but we will retain some data to prevent you from evading the suspension.
|
silence: You can still use your account but only people who are already following you will see your posts on this server, and you may be excluded from various discovery features. However, others may still manually follow you.
|
||||||
get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
|
suspend: You can no longer use your account, and your profile and other data are no longer accessible. You can still login to request a backup of your data until the data is fully removed in about 30 days, but we will retain some basic data to prevent you from evading the suspension.
|
||||||
|
get_in_touch: If you believe this is an error, you can reply to this e-mail to get in touch with the staff of %{instance}.
|
||||||
|
reason: 'Reason:'
|
||||||
review_server_policies: Review server policies
|
review_server_policies: Review server policies
|
||||||
statuses: 'Specifically, for:'
|
statuses: 'Posts that have been found in violation:'
|
||||||
subject:
|
subject:
|
||||||
|
delete_statuses: Your posts on %{acct} have been removed
|
||||||
disable: Your account %{acct} has been frozen
|
disable: Your account %{acct} has been frozen
|
||||||
none: Warning for %{acct}
|
none: Warning for %{acct}
|
||||||
sensitive: Your account %{acct} posting media has been marked as sensitive
|
sensitive: Your media files on %{acct} will be marked as sensitive from now on
|
||||||
silence: Your account %{acct} has been limited
|
silence: Your account %{acct} has been limited
|
||||||
suspend: Your account %{acct} has been suspended
|
suspend: Your account %{acct} has been suspended
|
||||||
title:
|
title:
|
||||||
|
delete_statuses: Posts removed
|
||||||
disable: Account frozen
|
disable: Account frozen
|
||||||
none: Warning
|
none: Warning
|
||||||
sensitive: Your media has been marked as sensitive
|
sensitive: Media hidden
|
||||||
silence: Account limited
|
silence: Account limited
|
||||||
suspend: Account suspended
|
suspend: Account suspended
|
||||||
welcome:
|
welcome:
|
||||||
|
|
|
@ -233,8 +233,6 @@ Rails.application.routes.draw do
|
||||||
post :reopen
|
post :reopen
|
||||||
post :resolve
|
post :resolve
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :reported_statuses, only: [:create]
|
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :report_notes, only: [:create, :destroy]
|
resources :report_notes, only: [:create, :destroy]
|
||||||
|
@ -261,7 +259,13 @@ Rails.application.routes.draw do
|
||||||
resource :change_email, only: [:show, :update]
|
resource :change_email, only: [:show, :update]
|
||||||
resource :reset, only: [:create]
|
resource :reset, only: [:create]
|
||||||
resource :action, only: [:new, :create], controller: 'account_actions'
|
resource :action, only: [:new, :create], controller: 'account_actions'
|
||||||
resources :statuses, only: [:index, :show, :create, :update, :destroy]
|
|
||||||
|
resources :statuses, only: [:index] do
|
||||||
|
collection do
|
||||||
|
post :batch
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
resources :relationships, only: [:index]
|
resources :relationships, only: [:index]
|
||||||
|
|
||||||
resource :confirmation, only: [:create] do
|
resource :confirmation, only: [:create] do
|
||||||
|
@ -518,7 +522,7 @@ Rails.application.routes.draw do
|
||||||
resource :action, only: [:create], controller: 'account_actions'
|
resource :action, only: [:create], controller: 'account_actions'
|
||||||
end
|
end
|
||||||
|
|
||||||
resources :reports, only: [:index, :show] do
|
resources :reports, only: [:index, :update, :show] do
|
||||||
member do
|
member do
|
||||||
post :assign_to_self
|
post :assign_to_self
|
||||||
post :unassign
|
post :unassign
|
||||||
|
|
|
@ -0,0 +1,21 @@
|
||||||
|
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
|
||||||
|
|
||||||
|
class AddCategoryToReports < ActiveRecord::Migration[6.1]
|
||||||
|
include Mastodon::MigrationHelpers
|
||||||
|
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
safety_assured { add_column_with_default :reports, :category, :int, default: 0, allow_null: false }
|
||||||
|
add_column :reports, :action_taken_at, :datetime
|
||||||
|
add_column :reports, :rule_ids, :bigint, array: true
|
||||||
|
safety_assured { execute 'UPDATE reports SET action_taken_at = updated_at WHERE action_taken = TRUE' }
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
safety_assured { execute 'UPDATE reports SET action_taken = TRUE WHERE action_taken_at IS NOT NULL' }
|
||||||
|
remove_column :reports, :category
|
||||||
|
remove_column :reports, :action_taken_at
|
||||||
|
remove_column :reports, :rule_ids
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,6 @@
|
||||||
|
class AddReportIdToAccountWarnings < ActiveRecord::Migration[6.1]
|
||||||
|
def change
|
||||||
|
safety_assured { add_reference :account_warnings, :report, foreign_key: { on_delete: :cascade }, index: false }
|
||||||
|
add_column :account_warnings, :status_ids, :string, array: true
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,21 @@
|
||||||
|
class FixAccountWarningActions < ActiveRecord::Migration[6.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def up
|
||||||
|
safety_assured do
|
||||||
|
execute 'UPDATE account_warnings SET action = 1000 WHERE action = 1'
|
||||||
|
execute 'UPDATE account_warnings SET action = 2000 WHERE action = 2'
|
||||||
|
execute 'UPDATE account_warnings SET action = 3000 WHERE action = 3'
|
||||||
|
execute 'UPDATE account_warnings SET action = 4000 WHERE action = 4'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
def down
|
||||||
|
safety_assured do
|
||||||
|
execute 'UPDATE account_warnings SET action = 1 WHERE action = 1000'
|
||||||
|
execute 'UPDATE account_warnings SET action = 2 WHERE action = 2000'
|
||||||
|
execute 'UPDATE account_warnings SET action = 3 WHERE action = 3000'
|
||||||
|
execute 'UPDATE account_warnings SET action = 4 WHERE action = 4000'
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,7 @@
|
||||||
|
class AddDeletedAtIndexOnStatuses < ActiveRecord::Migration[6.1]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
add_index :statuses, :deleted_at, where: 'deleted_at IS NOT NULL', algorithm: :concurrently
|
||||||
|
end
|
||||||
|
end
|
|
@ -0,0 +1,9 @@
|
||||||
|
# frozen_string_literal: true
|
||||||
|
|
||||||
|
class RemoveActionTakenFromReports < ActiveRecord::Migration[5.2]
|
||||||
|
disable_ddl_transaction!
|
||||||
|
|
||||||
|
def change
|
||||||
|
safety_assured { remove_column :reports, :action_taken, :boolean, default: false, null: false }
|
||||||
|
end
|
||||||
|
end
|
10
db/schema.rb
10
db/schema.rb
|
@ -10,7 +10,7 @@
|
||||||
#
|
#
|
||||||
# It's strongly recommended that you check this file into your version control system.
|
# It's strongly recommended that you check this file into your version control system.
|
||||||
|
|
||||||
ActiveRecord::Schema.define(version: 2021_12_13_040746) do
|
ActiveRecord::Schema.define(version: 2022_01_16_202951) do
|
||||||
|
|
||||||
# These are extensions that must be enabled in order to support this database
|
# These are extensions that must be enabled in order to support this database
|
||||||
enable_extension "plpgsql"
|
enable_extension "plpgsql"
|
||||||
|
@ -133,6 +133,8 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
|
||||||
t.text "text", default: "", null: false
|
t.text "text", default: "", null: false
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
|
t.bigint "report_id"
|
||||||
|
t.string "status_ids", array: true
|
||||||
t.index ["account_id"], name: "index_account_warnings_on_account_id"
|
t.index ["account_id"], name: "index_account_warnings_on_account_id"
|
||||||
t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id"
|
t.index ["target_account_id"], name: "index_account_warnings_on_target_account_id"
|
||||||
end
|
end
|
||||||
|
@ -747,7 +749,6 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
|
||||||
create_table "reports", force: :cascade do |t|
|
create_table "reports", force: :cascade do |t|
|
||||||
t.bigint "status_ids", default: [], null: false, array: true
|
t.bigint "status_ids", default: [], null: false, array: true
|
||||||
t.text "comment", default: "", null: false
|
t.text "comment", default: "", null: false
|
||||||
t.boolean "action_taken", default: false, null: false
|
|
||||||
t.datetime "created_at", null: false
|
t.datetime "created_at", null: false
|
||||||
t.datetime "updated_at", null: false
|
t.datetime "updated_at", null: false
|
||||||
t.bigint "account_id", null: false
|
t.bigint "account_id", null: false
|
||||||
|
@ -756,6 +757,9 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
|
||||||
t.bigint "assigned_account_id"
|
t.bigint "assigned_account_id"
|
||||||
t.string "uri"
|
t.string "uri"
|
||||||
t.boolean "forwarded"
|
t.boolean "forwarded"
|
||||||
|
t.integer "category", default: 0, null: false
|
||||||
|
t.datetime "action_taken_at"
|
||||||
|
t.bigint "rule_ids", array: true
|
||||||
t.index ["account_id"], name: "index_reports_on_account_id"
|
t.index ["account_id"], name: "index_reports_on_account_id"
|
||||||
t.index ["target_account_id"], name: "index_reports_on_target_account_id"
|
t.index ["target_account_id"], name: "index_reports_on_target_account_id"
|
||||||
end
|
end
|
||||||
|
@ -853,6 +857,7 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
|
||||||
t.string "content_type"
|
t.string "content_type"
|
||||||
t.datetime "deleted_at"
|
t.datetime "deleted_at"
|
||||||
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
|
t.index ["account_id", "id", "visibility", "updated_at"], name: "index_statuses_20190820", order: { id: :desc }, where: "(deleted_at IS NULL)"
|
||||||
|
t.index ["deleted_at"], name: "index_statuses_on_deleted_at", where: "(deleted_at IS NOT NULL)"
|
||||||
t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
|
t.index ["id", "account_id"], name: "index_statuses_local_20190824", order: { id: :desc }, where: "((local OR (uri IS NULL)) AND (deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
|
||||||
t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
|
t.index ["id", "account_id"], name: "index_statuses_public_20200119", order: { id: :desc }, where: "((deleted_at IS NULL) AND (visibility = 0) AND (reblog_of_id IS NULL) AND ((NOT reply) OR (in_reply_to_account_id = account_id)))"
|
||||||
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
t.index ["in_reply_to_account_id"], name: "index_statuses_on_in_reply_to_account_id"
|
||||||
|
@ -1010,6 +1015,7 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
|
||||||
add_foreign_key "account_statuses_cleanup_policies", "accounts", on_delete: :cascade
|
add_foreign_key "account_statuses_cleanup_policies", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "account_warnings", "accounts", column: "target_account_id", on_delete: :cascade
|
add_foreign_key "account_warnings", "accounts", column: "target_account_id", on_delete: :cascade
|
||||||
add_foreign_key "account_warnings", "accounts", on_delete: :nullify
|
add_foreign_key "account_warnings", "accounts", on_delete: :nullify
|
||||||
|
add_foreign_key "account_warnings", "reports", on_delete: :cascade
|
||||||
add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
|
add_foreign_key "accounts", "accounts", column: "moved_to_account_id", on_delete: :nullify
|
||||||
add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
|
add_foreign_key "admin_action_logs", "accounts", on_delete: :cascade
|
||||||
add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade
|
add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade
|
||||||
|
|
|
@ -12,11 +12,11 @@ describe Admin::ReportNotesController do
|
||||||
describe 'POST #create' do
|
describe 'POST #create' do
|
||||||
subject { post :create, params: params }
|
subject { post :create, params: params }
|
||||||
|
|
||||||
let(:report) { Fabricate(:report, action_taken: action_taken, action_taken_by_account_id: account_id) }
|
let(:report) { Fabricate(:report, action_taken_at: action_taken, action_taken_by_account_id: account_id) }
|
||||||
|
|
||||||
context 'when parameter is valid' do
|
context 'when parameter is valid' do
|
||||||
context 'when report is unsolved' do
|
context 'when report is unsolved' do
|
||||||
let(:action_taken) { false }
|
let(:action_taken) { nil }
|
||||||
let(:account_id) { nil }
|
let(:account_id) { nil }
|
||||||
|
|
||||||
context 'when create_and_resolve flag is on' do
|
context 'when create_and_resolve flag is on' do
|
||||||
|
@ -41,7 +41,7 @@ describe Admin::ReportNotesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when report is resolved' do
|
context 'when report is resolved' do
|
||||||
let(:action_taken) { true }
|
let(:action_taken) { Time.now.utc }
|
||||||
let(:account_id) { user.account.id }
|
let(:account_id) { user.account.id }
|
||||||
|
|
||||||
context 'when create_and_unresolve flag is on' do
|
context 'when create_and_unresolve flag is on' do
|
||||||
|
@ -68,7 +68,7 @@ describe Admin::ReportNotesController do
|
||||||
|
|
||||||
context 'when parameter is invalid' do
|
context 'when parameter is invalid' do
|
||||||
let(:params) { { report_note: { content: '', report_id: report.id } } }
|
let(:params) { { report_note: { content: '', report_id: report.id } } }
|
||||||
let(:action_taken) { false }
|
let(:action_taken) { nil }
|
||||||
let(:account_id) { nil }
|
let(:account_id) { nil }
|
||||||
|
|
||||||
it 'renders admin/reports/show' do
|
it 'renders admin/reports/show' do
|
||||||
|
|
|
@ -1,59 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
describe Admin::ReportedStatusesController do
|
|
||||||
render_views
|
|
||||||
|
|
||||||
let(:user) { Fabricate(:user, admin: true) }
|
|
||||||
let(:report) { Fabricate(:report, status_ids: [status.id]) }
|
|
||||||
let(:status) { Fabricate(:status) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
sign_in user, scope: :user
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'POST #create' do
|
|
||||||
subject do
|
|
||||||
-> { post :create, params: { :report_id => report, action => '', :form_status_batch => { status_ids: status_ids } } }
|
|
||||||
end
|
|
||||||
|
|
||||||
let(:action) { 'nsfw_on' }
|
|
||||||
let(:status_ids) { [status.id] }
|
|
||||||
let(:status) { Fabricate(:status, sensitive: !sensitive) }
|
|
||||||
let(:sensitive) { true }
|
|
||||||
let!(:media_attachment) { Fabricate(:media_attachment, status: status) }
|
|
||||||
|
|
||||||
context 'when action is nsfw_on' do
|
|
||||||
it 'updates sensitive column' do
|
|
||||||
is_expected.to change {
|
|
||||||
status.reload.sensitive
|
|
||||||
}.from(false).to(true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when action is nsfw_off' do
|
|
||||||
let(:action) { 'nsfw_off' }
|
|
||||||
let(:sensitive) { false }
|
|
||||||
|
|
||||||
it 'updates sensitive column' do
|
|
||||||
is_expected.to change {
|
|
||||||
status.reload.sensitive
|
|
||||||
}.from(true).to(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when action is delete' do
|
|
||||||
let(:action) { 'delete' }
|
|
||||||
|
|
||||||
it 'removes a status' do
|
|
||||||
allow(RemovalWorker).to receive(:perform_async)
|
|
||||||
subject.call
|
|
||||||
expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'redirects to report page' do
|
|
||||||
subject.call
|
|
||||||
expect(response).to redirect_to(admin_report_path(report))
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -10,8 +10,8 @@ describe Admin::ReportsController do
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
it 'returns http success with no filters' do
|
it 'returns http success with no filters' do
|
||||||
specified = Fabricate(:report, action_taken: false)
|
specified = Fabricate(:report, action_taken_at: nil)
|
||||||
Fabricate(:report, action_taken: true)
|
Fabricate(:report, action_taken_at: Time.now.utc)
|
||||||
|
|
||||||
get :index
|
get :index
|
||||||
|
|
||||||
|
@ -22,10 +22,10 @@ describe Admin::ReportsController do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success with resolved filter' do
|
it 'returns http success with resolved filter' do
|
||||||
specified = Fabricate(:report, action_taken: true)
|
specified = Fabricate(:report, action_taken_at: Time.now.utc)
|
||||||
Fabricate(:report, action_taken: false)
|
Fabricate(:report, action_taken_at: nil)
|
||||||
|
|
||||||
get :index, params: { resolved: 1 }
|
get :index, params: { resolved: '1' }
|
||||||
|
|
||||||
reports = assigns(:reports).to_a
|
reports = assigns(:reports).to_a
|
||||||
expect(reports.size).to eq 1
|
expect(reports.size).to eq 1
|
||||||
|
@ -54,15 +54,7 @@ describe Admin::ReportsController do
|
||||||
expect(response).to redirect_to(admin_reports_path)
|
expect(response).to redirect_to(admin_reports_path)
|
||||||
report.reload
|
report.reload
|
||||||
expect(report.action_taken_by_account).to eq user.account
|
expect(report.action_taken_by_account).to eq user.account
|
||||||
expect(report.action_taken).to eq true
|
expect(report.action_taken?).to eq true
|
||||||
end
|
|
||||||
|
|
||||||
it 'sets trust level when the report is an antispam one' do
|
|
||||||
report = Fabricate(:report, account: Account.representative)
|
|
||||||
|
|
||||||
put :resolve, params: { id: report }
|
|
||||||
report.reload
|
|
||||||
expect(report.target_account.trust_level).to eq Account::TRUST_LEVELS[:trusted]
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
@ -74,7 +66,7 @@ describe Admin::ReportsController do
|
||||||
expect(response).to redirect_to(admin_report_path(report))
|
expect(response).to redirect_to(admin_report_path(report))
|
||||||
report.reload
|
report.reload
|
||||||
expect(report.action_taken_by_account).to eq nil
|
expect(report.action_taken_by_account).to eq nil
|
||||||
expect(report.action_taken).to eq false
|
expect(report.action_taken?).to eq false
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
|
|
@ -18,65 +18,46 @@ describe Admin::StatusesController do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'GET #index' do
|
describe 'GET #index' do
|
||||||
it 'returns http success with no media' do
|
context do
|
||||||
|
before do
|
||||||
get :index, params: { account_id: account.id }
|
get :index, params: { account_id: account.id }
|
||||||
|
|
||||||
statuses = assigns(:statuses).to_a
|
|
||||||
expect(statuses.size).to eq 4
|
|
||||||
expect(statuses.first.id).to eq last_status.id
|
|
||||||
expect(response).to have_http_status(200)
|
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'returns http success with media' do
|
it 'returns http success' do
|
||||||
get :index, params: { account_id: account.id, media: true }
|
|
||||||
|
|
||||||
statuses = assigns(:statuses).to_a
|
|
||||||
expect(statuses.size).to eq 2
|
|
||||||
expect(statuses.first.id).to eq last_media_attached_status.id
|
|
||||||
expect(response).to have_http_status(200)
|
expect(response).to have_http_status(200)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'POST #create' do
|
context 'filtering by media' do
|
||||||
subject do
|
before do
|
||||||
-> { post :create, params: { :account_id => account.id, action => '', :form_status_batch => { status_ids: status_ids } } }
|
get :index, params: { account_id: account.id, media: '1' }
|
||||||
|
end
|
||||||
|
|
||||||
|
it 'returns http success' do
|
||||||
|
expect(response).to have_http_status(200)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
describe 'POST #batch' do
|
||||||
|
before do
|
||||||
|
post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } }
|
||||||
end
|
end
|
||||||
|
|
||||||
let(:action) { 'nsfw_on' }
|
|
||||||
let(:status_ids) { [media_attached_status.id] }
|
let(:status_ids) { [media_attached_status.id] }
|
||||||
|
|
||||||
context 'when action is nsfw_on' do
|
context 'when action is report' do
|
||||||
it 'updates sensitive column' do
|
let(:action) { 'report' }
|
||||||
is_expected.to change {
|
|
||||||
media_attached_status.reload.sensitive
|
it 'creates a report' do
|
||||||
}.from(false).to(true)
|
report = Report.last
|
||||||
end
|
expect(report.target_account_id).to eq account.id
|
||||||
|
expect(report.status_ids).to eq status_ids
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'when action is nsfw_off' do
|
it 'redirects to report page' do
|
||||||
let(:action) { 'nsfw_off' }
|
expect(response).to redirect_to(admin_report_path(Report.last.id))
|
||||||
let(:sensitive) { false }
|
end
|
||||||
|
|
||||||
it 'updates sensitive column' do
|
|
||||||
is_expected.to change {
|
|
||||||
media_attached_status.reload.sensitive
|
|
||||||
}.from(true).to(false)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'when action is delete' do
|
|
||||||
let(:action) { 'delete' }
|
|
||||||
|
|
||||||
it 'removes a status' do
|
|
||||||
allow(RemovalWorker).to receive(:perform_async)
|
|
||||||
subject.call
|
|
||||||
expect(RemovalWorker).to have_received(:perform_async).with(status_ids.first, immediate: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'redirects to account statuses page' do
|
|
||||||
subject.call
|
|
||||||
expect(response).to redirect_to(admin_account_statuses_path(account.id))
|
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
|
@ -2,5 +2,5 @@ Fabricator(:report) do
|
||||||
account
|
account
|
||||||
target_account { Fabricate(:account) }
|
target_account { Fabricate(:account) }
|
||||||
comment "You nasty"
|
comment "You nasty"
|
||||||
action_taken false
|
action_taken_at nil
|
||||||
end
|
end
|
||||||
|
|
|
@ -79,7 +79,7 @@ class UserMailerPreview < ActionMailer::Preview
|
||||||
|
|
||||||
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning
|
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning
|
||||||
def warning
|
def warning
|
||||||
UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id])
|
UserMailer.warning(User.first, AccountWarning.last)
|
||||||
end
|
end
|
||||||
|
|
||||||
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token
|
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token
|
||||||
|
|
|
@ -1,52 +0,0 @@
|
||||||
require 'rails_helper'
|
|
||||||
|
|
||||||
describe Form::StatusBatch do
|
|
||||||
let(:form) { Form::StatusBatch.new(action: action, status_ids: status_ids) }
|
|
||||||
let(:status) { Fabricate(:status) }
|
|
||||||
|
|
||||||
describe 'with nsfw action' do
|
|
||||||
let(:status_ids) { [status.id, nonsensitive_status.id, sensitive_status.id] }
|
|
||||||
let(:nonsensitive_status) { Fabricate(:status, sensitive: false) }
|
|
||||||
let(:sensitive_status) { Fabricate(:status, sensitive: true) }
|
|
||||||
let!(:shown_media_attachment) { Fabricate(:media_attachment, status: nonsensitive_status) }
|
|
||||||
let!(:hidden_media_attachment) { Fabricate(:media_attachment, status: sensitive_status) }
|
|
||||||
|
|
||||||
context 'nsfw_on' do
|
|
||||||
let(:action) { 'nsfw_on' }
|
|
||||||
|
|
||||||
it { expect(form.save).to be true }
|
|
||||||
it { expect { form.save }.to change { nonsensitive_status.reload.sensitive }.from(false).to(true) }
|
|
||||||
it { expect { form.save }.not_to change { sensitive_status.reload.sensitive } }
|
|
||||||
it { expect { form.save }.not_to change { status.reload.sensitive } }
|
|
||||||
end
|
|
||||||
|
|
||||||
context 'nsfw_off' do
|
|
||||||
let(:action) { 'nsfw_off' }
|
|
||||||
|
|
||||||
it { expect(form.save).to be true }
|
|
||||||
it { expect { form.save }.to change { sensitive_status.reload.sensitive }.from(true).to(false) }
|
|
||||||
it { expect { form.save }.not_to change { nonsensitive_status.reload.sensitive } }
|
|
||||||
it { expect { form.save }.not_to change { status.reload.sensitive } }
|
|
||||||
end
|
|
||||||
end
|
|
||||||
|
|
||||||
describe 'with delete action' do
|
|
||||||
let(:status_ids) { [status.id] }
|
|
||||||
let(:action) { 'delete' }
|
|
||||||
let!(:another_status) { Fabricate(:status) }
|
|
||||||
|
|
||||||
before do
|
|
||||||
allow(RemovalWorker).to receive(:perform_async)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'call RemovalWorker' do
|
|
||||||
form.save
|
|
||||||
expect(RemovalWorker).to have_received(:perform_async).with(status.id, immediate: true)
|
|
||||||
end
|
|
||||||
|
|
||||||
it 'do not call RemovalWorker' do
|
|
||||||
form.save
|
|
||||||
expect(RemovalWorker).not_to have_received(:perform_async).with(another_status.id, immediate: true)
|
|
||||||
end
|
|
||||||
end
|
|
||||||
end
|
|
|
@ -54,7 +54,7 @@ describe Report do
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'resolve!' do
|
describe 'resolve!' do
|
||||||
subject(:report) { Fabricate(:report, action_taken: false, action_taken_by_account_id: nil) }
|
subject(:report) { Fabricate(:report, action_taken_at: nil, action_taken_by_account_id: nil) }
|
||||||
|
|
||||||
let(:acting_account) { Fabricate(:account) }
|
let(:acting_account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
@ -63,12 +63,13 @@ describe Report do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'records action taken' do
|
it 'records action taken' do
|
||||||
expect(report).to have_attributes(action_taken: true, action_taken_by_account_id: acting_account.id)
|
expect(report.action_taken?).to be true
|
||||||
|
expect(report.action_taken_by_account_id).to eq acting_account.id
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'unresolve!' do
|
describe 'unresolve!' do
|
||||||
subject(:report) { Fabricate(:report, action_taken: true, action_taken_by_account_id: acting_account.id) }
|
subject(:report) { Fabricate(:report, action_taken_at: Time.now.utc, action_taken_by_account_id: acting_account.id) }
|
||||||
|
|
||||||
let(:acting_account) { Fabricate(:account) }
|
let(:acting_account) { Fabricate(:account) }
|
||||||
|
|
||||||
|
@ -77,23 +78,24 @@ describe Report do
|
||||||
end
|
end
|
||||||
|
|
||||||
it 'unresolves' do
|
it 'unresolves' do
|
||||||
expect(report).to have_attributes(action_taken: false, action_taken_by_account_id: nil)
|
expect(report.action_taken?).to be false
|
||||||
|
expect(report.action_taken_by_account_id).to be_nil
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
describe 'unresolved?' do
|
describe 'unresolved?' do
|
||||||
subject { report.unresolved? }
|
subject { report.unresolved? }
|
||||||
|
|
||||||
let(:report) { Fabricate(:report, action_taken: action_taken) }
|
let(:report) { Fabricate(:report, action_taken_at: action_taken) }
|
||||||
|
|
||||||
context 'if action is taken' do
|
context 'if action is taken' do
|
||||||
let(:action_taken) { true }
|
let(:action_taken) { Time.now.utc }
|
||||||
|
|
||||||
it { is_expected.to be false }
|
it { is_expected.to be false }
|
||||||
end
|
end
|
||||||
|
|
||||||
context 'if action not is taken' do
|
context 'if action not is taken' do
|
||||||
let(:action_taken) { false }
|
let(:action_taken) { nil }
|
||||||
|
|
||||||
it { is_expected.to be true }
|
it { is_expected.to be true }
|
||||||
end
|
end
|
||||||
|
|
Loading…
Reference in New Issue