diff --git a/app/controllers/admin/account_moderation_notes_controller.rb b/app/controllers/admin/account_moderation_notes_controller.rb
index 44f6e34f80b..4f36f33f47e 100644
--- a/app/controllers/admin/account_moderation_notes_controller.rb
+++ b/app/controllers/admin/account_moderation_notes_controller.rb
@@ -14,7 +14,7 @@ module Admin
else
@account = @account_moderation_note.target_account
@moderation_notes = @account.targeted_moderation_notes.latest
- @warnings = @account.targeted_account_warnings.latest.custom
+ @warnings = @account.strikes.custom.latest
render template: 'admin/accounts/show'
end
diff --git a/app/controllers/admin/accounts_controller.rb b/app/controllers/admin/accounts_controller.rb
index 0786985fac3..e7f56e243bb 100644
--- a/app/controllers/admin/accounts_controller.rb
+++ b/app/controllers/admin/accounts_controller.rb
@@ -28,7 +28,7 @@ module Admin
@deletion_request = @account.deletion_request
@account_moderation_note = current_account.account_moderation_notes.new(target_account: @account)
@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)
end
diff --git a/app/controllers/admin/report_notes_controller.rb b/app/controllers/admin/report_notes_controller.rb
index b816c5b5d48..3fd815b60a0 100644
--- a/app/controllers/admin/report_notes_controller.rb
+++ b/app/controllers/admin/report_notes_controller.rb
@@ -14,20 +14,17 @@ module Admin
if params[:create_and_resolve]
@report.resolve!(current_account)
log_action :resolve, @report
-
- redirect_to admin_reports_path, notice: I18n.t('admin.reports.resolved_msg')
- return
- end
-
- if params[:create_and_unresolve]
+ elsif params[:create_and_unresolve]
@report.unresolve!
log_action :reopen, @report
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
- @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
- @form = Form::StatusBatch.new
+ @report_notes = @report.notes.includes(:account).order(id: :desc)
+ @action_logs = @report.history.includes(:target)
+ @form = Admin::StatusBatchAction.new
+ @statuses = @report.statuses.with_includes
render template: 'admin/reports/show'
end
@@ -41,6 +38,14 @@ module Admin
private
+ def after_create_redirect_path
+ if params[:create_and_resolve]
+ admin_reports_path
+ else
+ admin_report_path(@report)
+ end
+ end
+
def resource_params
params.require(:report_note).permit(
:content,
diff --git a/app/controllers/admin/reported_statuses_controller.rb b/app/controllers/admin/reported_statuses_controller.rb
deleted file mode 100644
index 3ba9f5df21e..00000000000
--- a/app/controllers/admin/reported_statuses_controller.rb
+++ /dev/null
@@ -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
diff --git a/app/controllers/admin/reports_controller.rb b/app/controllers/admin/reports_controller.rb
index 7c831b3d4c3..00d200d7c88 100644
--- a/app/controllers/admin/reports_controller.rb
+++ b/app/controllers/admin/reports_controller.rb
@@ -13,8 +13,10 @@ module Admin
authorize @report, :show?
@report_note = @report.notes.new
- @report_notes = (@report.notes.latest + @report.history + @report.target_account.targeted_account_warnings.latest.custom).sort_by(&:created_at)
- @form = Form::StatusBatch.new
+ @report_notes = @report.notes.includes(:account).order(id: :desc)
+ @action_logs = @report.history.includes(:target)
+ @form = Admin::StatusBatchAction.new
+ @statuses = @report.statuses.with_includes
end
def assign_to_self
diff --git a/app/controllers/admin/statuses_controller.rb b/app/controllers/admin/statuses_controller.rb
index b3fd4c42465..8d039b2810b 100644
--- a/app/controllers/admin/statuses_controller.rb
+++ b/app/controllers/admin/statuses_controller.rb
@@ -2,71 +2,57 @@
module Admin
class StatusesController < BaseController
- helper_method :current_params
-
before_action :set_account
+ before_action :set_statuses
PER_PAGE = 20
def index
authorize :status, :index?
- @statuses = @account.statuses.where(visibility: [:public, :unlisted])
-
- if params[:media]
- @statuses = @statuses.merge(Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)).reorder('statuses.id desc')
- end
-
- @statuses = @statuses.preload(:media_attachments, :mentions).page(params[:page]).per(PER_PAGE)
- @form = Form::StatusBatch.new
+ @status_batch_action = Admin::StatusBatchAction.new
end
- 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)
+ def batch
+ @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))
+ @status_batch_action.save!
rescue ActionController::ParameterMissing
flash[:alert] = I18n.t('admin.statuses.no_status_selected')
-
- redirect_to admin_account_statuses_path(@account.id, current_params)
+ ensure
+ redirect_to after_create_redirect_path
end
private
- def form_status_batch_params
- params.require(:form_status_batch).permit(:action, status_ids: [])
+ def admin_status_batch_action_params
+ 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
def set_account
@account = Account.find(params[:account_id])
end
- def current_params
- page = (params[:page] || 1).to_i
+ def set_statuses
+ @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
- {
- media: params[:media],
- page: page > 1 && page,
- }.select { |_, value| value.present? }
+ def filter_params
+ params.slice(*Admin::StatusFilter::KEYS).permit(*Admin::StatusFilter::KEYS)
end
def action_from_button
- if params[:nsfw_on]
- 'nsfw_on'
- elsif params[:nsfw_off]
- 'nsfw_off'
+ if params[:report]
+ 'report'
+ elsif params[:remove_from_report]
+ 'remove_from_report'
elsif params[:delete]
'delete'
end
diff --git a/app/controllers/api/v1/admin/account_actions_controller.rb b/app/controllers/api/v1/admin/account_actions_controller.rb
index 29c9b7107bf..15af50822e3 100644
--- a/app/controllers/api/v1/admin/account_actions_controller.rb
+++ b/app/controllers/api/v1/admin/account_actions_controller.rb
@@ -1,7 +1,9 @@
# frozen_string_literal: true
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 :set_account
diff --git a/app/controllers/api/v1/admin/accounts_controller.rb b/app/controllers/api/v1/admin/accounts_controller.rb
index 9b8f2fb059f..65330b8c814 100644
--- a/app/controllers/api/v1/admin/accounts_controller.rb
+++ b/app/controllers/api/v1/admin/accounts_controller.rb
@@ -1,13 +1,15 @@
# frozen_string_literal: true
class Api::V1::Admin::AccountsController < Api::BaseController
+ protect_from_forgery with: :exception
+
include Authorization
include AccountableConcern
LIMIT = 100
- before_action -> { doorkeeper_authorize! :'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:read', :'admin:read:accounts' }, only: [:index, :show]
+ before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:accounts' }, except: [:index, :show]
before_action :require_staff!
before_action :set_accounts, only: :index
before_action :set_account, except: :index
diff --git a/app/controllers/api/v1/admin/dimensions_controller.rb b/app/controllers/api/v1/admin/dimensions_controller.rb
index 5e8f0f89f01..b1f73899019 100644
--- a/app/controllers/api/v1/admin/dimensions_controller.rb
+++ b/app/controllers/api/v1/admin/dimensions_controller.rb
@@ -3,6 +3,7 @@
class Api::V1::Admin::DimensionsController < Api::BaseController
protect_from_forgery with: :exception
+ before_action -> { authorize_if_got_token! :'admin:read' }
before_action :require_staff!
before_action :set_dimensions
diff --git a/app/controllers/api/v1/admin/measures_controller.rb b/app/controllers/api/v1/admin/measures_controller.rb
index f2819175345..d64c3cdf704 100644
--- a/app/controllers/api/v1/admin/measures_controller.rb
+++ b/app/controllers/api/v1/admin/measures_controller.rb
@@ -3,6 +3,7 @@
class Api::V1::Admin::MeasuresController < Api::BaseController
protect_from_forgery with: :exception
+ before_action -> { authorize_if_got_token! :'admin:read' }
before_action :require_staff!
before_action :set_measures
diff --git a/app/controllers/api/v1/admin/reports_controller.rb b/app/controllers/api/v1/admin/reports_controller.rb
index c8f4cd8d80c..fbfd0ee128c 100644
--- a/app/controllers/api/v1/admin/reports_controller.rb
+++ b/app/controllers/api/v1/admin/reports_controller.rb
@@ -1,13 +1,15 @@
# frozen_string_literal: true
class Api::V1::Admin::ReportsController < Api::BaseController
+ protect_from_forgery with: :exception
+
include Authorization
include AccountableConcern
LIMIT = 100
- before_action -> { doorkeeper_authorize! :'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:read', :'admin:read:reports' }, only: [:index, :show]
+ before_action -> { authorize_if_got_token! :'admin:write', :'admin:write:reports' }, except: [:index, :show]
before_action :require_staff!
before_action :set_reports, only: :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
end
+ def update
+ authorize @report, :update?
+ @report.update!(report_params)
+ render json: @report, serializer: REST::Admin::ReportSerializer
+ end
+
def assign_to_self
authorize @report, :update?
@report.update!(assigned_account_id: current_account.id)
@@ -74,6 +82,10 @@ class Api::V1::Admin::ReportsController < Api::BaseController
ReportFilter.new(filter_params).results
end
+ def report_params
+ params.permit(:category, rule_ids: [])
+ end
+
def filter_params
params.permit(*FILTER_PARAMS)
end
diff --git a/app/controllers/api/v1/admin/retention_controller.rb b/app/controllers/api/v1/admin/retention_controller.rb
index a8ff64f21d0..4af5a5c4dcc 100644
--- a/app/controllers/api/v1/admin/retention_controller.rb
+++ b/app/controllers/api/v1/admin/retention_controller.rb
@@ -3,6 +3,7 @@
class Api::V1::Admin::RetentionController < Api::BaseController
protect_from_forgery with: :exception
+ before_action -> { authorize_if_got_token! :'admin:read' }
before_action :require_staff!
before_action :set_cohorts
diff --git a/app/controllers/api/v1/admin/trends/tags_controller.rb b/app/controllers/api/v1/admin/trends/tags_controller.rb
index 3653d1dd13b..4815af31ea9 100644
--- a/app/controllers/api/v1/admin/trends/tags_controller.rb
+++ b/app/controllers/api/v1/admin/trends/tags_controller.rb
@@ -1,6 +1,9 @@
# frozen_string_literal: true
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 :set_tags
diff --git a/app/helpers/admin/filter_helper.rb b/app/helpers/admin/filter_helper.rb
index 5f69f176a6a..907529b3723 100644
--- a/app/helpers/admin/filter_helper.rb
+++ b/app/helpers/admin/filter_helper.rb
@@ -13,6 +13,7 @@ module Admin::FilterHelper
RelationshipFilter::KEYS,
AnnouncementFilter::KEYS,
Admin::ActionLogFilter::KEYS,
+ Admin::StatusFilter::KEYS,
].flatten.freeze
def filter_link_to(text, link_to_params, link_class_params = link_to_params)
diff --git a/app/javascript/mastodon/components/admin/ReportReasonSelector.js b/app/javascript/mastodon/components/admin/ReportReasonSelector.js
new file mode 100644
index 00000000000..1f91d25175a
--- /dev/null
+++ b/app/javascript/mastodon/components/admin/ReportReasonSelector.js
@@ -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 (
+
+ {selected &&
}
+
+
+
+ {text}
+
+
+ {(selected && children) && (
+
+ {children}
+
+ )}
+
+ );
+ }
+
+}
+
+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 (
+
+
+ {selected && }
+ {text}
+
+ );
+ }
+
+}
+
+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 (
+
+
+
+
+ {rules.map(rule => )}
+
+
+ );
+ }
+
+}
diff --git a/app/javascript/mastodon/components/status_action_bar.js b/app/javascript/mastodon/components/status_action_bar.js
index d125359e9e7..4e19cc0e46d 100644
--- a/app/javascript/mastodon/components/status_action_bar.js
+++ b/app/javascript/mastodon/components/status_action_bar.js
@@ -291,7 +291,7 @@ class StatusActionBar extends ImmutablePureComponent {
if (isStaff) {
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_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')}` });
}
}
diff --git a/app/javascript/mastodon/features/status/components/action_bar.js b/app/javascript/mastodon/features/status/components/action_bar.js
index e60119bc47a..a15a4d567a8 100644
--- a/app/javascript/mastodon/features/status/components/action_bar.js
+++ b/app/javascript/mastodon/features/status/components/action_bar.js
@@ -245,7 +245,7 @@ class ActionBar extends React.PureComponent {
if (isStaff) {
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_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')}` });
}
}
diff --git a/app/javascript/styles/mailer.scss b/app/javascript/styles/mailer.scss
index 92c02e847d9..34852178e35 100644
--- a/app/javascript/styles/mailer.scss
+++ b/app/javascript/styles/mailer.scss
@@ -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) {
body {
min-height: 1024px !important;
diff --git a/app/javascript/styles/mastodon/admin.scss b/app/javascript/styles/mastodon/admin.scss
index 8fd556c7362..92061585a54 100644
--- a/app/javascript/styles/mastodon/admin.scss
+++ b/app/javascript/styles/mastodon/admin.scss
@@ -595,39 +595,44 @@ body,
.log-entry {
line-height: 20px;
- padding: 15px 0;
+ padding: 15px;
+ padding-left: 15px * 2 + 40px;
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 {
+ border-bottom-left-radius: 4px;
+ border-bottom-right-radius: 4px;
border-bottom: 0;
}
+ &:hover {
+ background: lighten($ui-base-color, 4%);
+ }
+
&__header {
- display: flex;
- justify-content: flex-start;
- align-items: center;
color: $darker-text-color;
font-size: 14px;
- padding: 0 10px;
}
&__avatar {
- margin-right: 10px;
+ position: absolute;
+ left: 15px;
+ top: 15px;
.avatar {
- display: block;
- margin: 0;
- border-radius: 50%;
+ border-radius: 4px;
width: 40px;
height: 40px;
}
}
- &__content {
- max-width: calc(100% - 90px);
- }
-
&__title {
word-wrap: break-word;
}
@@ -643,6 +648,14 @@ body,
text-decoration: none;
font-weight: 500;
}
+
+ a {
+ &:hover,
+ &:focus,
+ &:active {
+ text-decoration: underline;
+ }
+ }
}
a.name-tag,
@@ -671,8 +684,9 @@ a.inline-name-tag,
a.name-tag,
.name-tag {
- display: flex;
+ display: inline-flex;
align-items: center;
+ vertical-align: top;
.avatar {
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;
+ }
+ }
+}
diff --git a/app/javascript/styles/mastodon/polls.scss b/app/javascript/styles/mastodon/polls.scss
index ad70889828b..e33fc798359 100644
--- a/app/javascript/styles/mastodon/polls.scss
+++ b/app/javascript/styles/mastodon/polls.scss
@@ -143,6 +143,21 @@
&:active {
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 {
diff --git a/app/lib/admin/metrics/measure/resolved_reports_measure.rb b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
index 0dcecbbad68..00cb24f7e1d 100644
--- a/app/lib/admin/metrics/measure/resolved_reports_measure.rb
+++ b/app/lib/admin/metrics/measure/resolved_reports_measure.rb
@@ -6,11 +6,11 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
end
def total
- Report.resolved.where(updated_at: time_period).count
+ Report.resolved.where(action_taken_at: time_period).count
end
def previous_total
- Report.resolved.where(updated_at: previous_time_period).count
+ Report.resolved.where(action_taken_at: previous_time_period).count
end
def data
@@ -19,8 +19,7 @@ class Admin::Metrics::Measure::ResolvedReportsMeasure < Admin::Metrics::Measure:
WITH resolved_reports AS (
SELECT reports.id
FROM reports
- WHERE action_taken
- AND date_trunc('day', reports.updated_at)::date = axis.period
+ WHERE date_trunc('day', reports.action_taken_at)::date = axis.period
)
SELECT count(*) FROM resolved_reports
) AS value
diff --git a/app/mailers/user_mailer.rb b/app/mailers/user_mailer.rb
index 68d1c4507e8..5221a48928e 100644
--- a/app/mailers/user_mailer.rb
+++ b/app/mailers/user_mailer.rb
@@ -160,11 +160,11 @@ class UserMailer < Devise::Mailer
end
end
- def warning(user, warning, status_ids = nil)
+ def warning(user, warning)
@resource = user
@warning = warning
@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
mail to: @resource.email,
diff --git a/app/models/account_warning.rb b/app/models/account_warning.rb
index 5efc924d5f7..fc0d988fdc0 100644
--- a/app/models/account_warning.rb
+++ b/app/models/account_warning.rb
@@ -10,14 +10,30 @@
# text :text default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
+# report_id :bigint(8)
+# status_ids :string is an Array
#
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 :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: '') }
+
+ def statuses
+ Status.with_discarded.where(id: status_ids || [])
+ end
end
diff --git a/app/models/admin/account_action.rb b/app/models/admin/account_action.rb
index bf222391f7e..d3be4be3fda 100644
--- a/app/models/admin/account_action.rb
+++ b/app/models/admin/account_action.rb
@@ -33,7 +33,7 @@ class Admin::AccountAction
def save!
ApplicationRecord.transaction do
process_action!
- process_warning!
+ process_strike!
end
process_email!
@@ -74,20 +74,14 @@ class Admin::AccountAction
end
end
- def process_warning!
- return unless warnable?
-
- authorize(target_account, :warn?)
-
- @warning = AccountWarning.create!(target_account: target_account,
- account: current_account,
- action: type,
- text: text_for_warning)
-
- # 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?
+ def process_strike!
+ @warning = target_account.strikes.create!(
+ account: current_account,
+ report: report,
+ action: type,
+ text: text_for_warning,
+ status_ids: status_ids
+ )
end
def process_reports!
@@ -143,7 +137,7 @@ class Admin::AccountAction
end
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
def warnable?
@@ -151,7 +145,7 @@ class Admin::AccountAction
end
def status_ids
- report.status_ids if report && include_statuses
+ report.status_ids if with_report? && include_statuses
end
def reports
diff --git a/app/models/admin/status_batch_action.rb b/app/models/admin/status_batch_action.rb
new file mode 100644
index 00000000000..319deff98e8
--- /dev/null
+++ b/app/models/admin/status_batch_action.rb
@@ -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
diff --git a/app/models/admin/status_filter.rb b/app/models/admin/status_filter.rb
new file mode 100644
index 00000000000..ce5bb5f4610
--- /dev/null
+++ b/app/models/admin/status_filter.rb
@@ -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
diff --git a/app/models/concerns/account_associations.rb b/app/models/concerns/account_associations.rb
index f9e7a3bea77..bbe269e8f0b 100644
--- a/app/models/concerns/account_associations.rb
+++ b/app/models/concerns/account_associations.rb
@@ -42,7 +42,7 @@ module AccountAssociations
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 :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)
has_many :list_accounts, inverse_of: :account, dependent: :destroy
diff --git a/app/models/form/status_batch.rb b/app/models/form/status_batch.rb
deleted file mode 100644
index c4943a7eabd..00000000000
--- a/app/models/form/status_batch.rb
+++ /dev/null
@@ -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
diff --git a/app/models/report.rb b/app/models/report.rb
index ef41547d99c..ceb15133b1b 100644
--- a/app/models/report.rb
+++ b/app/models/report.rb
@@ -6,7 +6,6 @@
# id :bigint(8) not null, primary key
# status_ids :bigint(8) default([]), not null, is an Array
# comment :text default(""), not null
-# action_taken :boolean default(FALSE), not null
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint(8) not null
@@ -15,9 +14,14 @@
# assigned_account_id :bigint(8)
# uri :string
# forwarded :boolean
+# category :integer default("other"), not null
+# action_taken_at :datetime
+# rule_ids :bigint(8) is an Array
#
class Report < ApplicationRecord
+ self.ignored_columns = %w(action_taken)
+
include Paginable
include RateLimitable
@@ -30,11 +34,17 @@ class Report < ApplicationRecord
has_many :notes, class_name: 'ReportNote', foreign_key: :report_id, inverse_of: :report, dependent: :destroy
- scope :unresolved, -> { where(action_taken: false) }
- scope :resolved, -> { where(action_taken: true) }
+ scope :unresolved, -> { where(action_taken_at: nil) }
+ 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] })) }
- validates :comment, length: { maximum: 1000 }
+ validates :comment, length: { maximum: 1_000 }
+
+ enum category: {
+ other: 0,
+ spam: 1_000,
+ violation: 2_000,
+ }
def local?
false # Force uri_for to use uri attribute
@@ -47,13 +57,17 @@ class Report < ApplicationRecord
end
def statuses
- Status.with_discarded.where(id: status_ids).includes(:account, :media_attachments, :mentions)
+ Status.with_discarded.where(id: status_ids)
end
def media_attachments
MediaAttachment.where(status_id: status_ids)
end
+ def rules
+ Rule.with_discarded.where(id: rule_ids)
+ end
+
def assign_to_self!(current_account)
update!(assigned_account_id: current_account.id)
end
@@ -63,22 +77,19 @@ class Report < ApplicationRecord
end
def resolve!(acting_account)
- if account_id == -99 && target_account.trust_level == Account::TRUST_LEVELS[:untrusted]
- # 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)
+ update!(action_taken_at: Time.now.utc, action_taken_by_account_id: acting_account.id)
end
def unresolve!
- update!(action_taken: false, action_taken_by_account_id: nil)
+ update!(action_taken_at: nil, action_taken_by_account_id: nil)
end
+ def action_taken?
+ action_taken_at.present?
+ end
+
+ alias action_taken action_taken?
+
def unresolved?
!action_taken?
end
@@ -88,29 +99,24 @@ class Report < ApplicationRecord
end
def history
- time_range = created_at..updated_at
-
- sql = [
+ subquery = [
Admin::ActionLog.where(
target_type: 'Report',
- target_id: id,
- created_at: time_range
- ).unscope(:order),
+ target_id: id
+ ).unscope(:order).arel,
Admin::ActionLog.where(
target_type: 'Account',
- target_id: target_account_id,
- created_at: time_range
- ).unscope(:order),
+ target_id: target_account_id
+ ).unscope(:order).arel,
Admin::ActionLog.where(
target_type: 'Status',
- target_id: status_ids,
- created_at: time_range
- ).unscope(:order),
- ].map { |query| "(#{query.to_sql})" }.join(' UNION ALL ')
+ target_id: status_ids
+ ).unscope(:order).arel,
+ ].reduce { |union, query| Arel::Nodes::UnionAll.new(union, query) }
- Admin::ActionLog.from("(#{sql}) AS admin_action_logs")
+ Admin::ActionLog.from(Arel::Nodes::As.new(subquery, Admin::ActionLog.arel_table))
end
def set_uri
diff --git a/app/models/report_filter.rb b/app/models/report_filter.rb
index a91a6baeb25..dc444a5520f 100644
--- a/app/models/report_filter.rb
+++ b/app/models/report_filter.rb
@@ -19,7 +19,7 @@ class ReportFilter
scope = Report.unresolved
params.each do |key, value|
- scope = scope.merge scope_for(key, value)
+ scope = scope.merge scope_for(key, value), rewhere: true
end
scope
diff --git a/app/serializers/rest/admin/report_serializer.rb b/app/serializers/rest/admin/report_serializer.rb
index 7a77132c038..74bc0c52028 100644
--- a/app/serializers/rest/admin/report_serializer.rb
+++ b/app/serializers/rest/admin/report_serializer.rb
@@ -1,7 +1,7 @@
# frozen_string_literal: true
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 :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_many :statuses, serializer: REST::StatusSerializer
+ has_many :rules, serializer: REST::RuleSerializer
def id
object.id.to_s
end
+
+ def statuses
+ object.statuses.with_includes
+ end
end
diff --git a/app/services/remove_status_service.rb b/app/services/remove_status_service.rb
index 9259c69d9f8..2fe3bab5cb9 100644
--- a/app/services/remove_status_service.rb
+++ b/app/services/remove_status_service.rb
@@ -9,6 +9,7 @@ class RemoveStatusService < BaseService
# @param [Hash] options
# @option [Boolean] :redraft
# @option [Boolean] :immediate
+ # @option [Boolean] :preserve
# @option [Boolean] :original_removed
def call(status, **options)
@payload = Oj.dump(event: :delete, payload: status.id.to_s)
@@ -44,7 +45,7 @@ class RemoveStatusService < BaseService
remove_media
end
- @status.destroy! if @options[:immediate] || !@status.reported?
+ @status.destroy! if permanently?
else
raise Mastodon::RaceConditionError
end
@@ -143,11 +144,15 @@ class RemoveStatusService < BaseService
end
def remove_media
- return if @options[:redraft] || (!@options[:immediate] && @status.reported?)
+ return if @options[:redraft] || !permanently?
@status.media_attachments.destroy_all
end
+ def permanently?
+ @options[:immediate] || !(@options[:preserve] || @status.reported?)
+ end
+
def lock_options
{ redis: Redis.current, key: "distribute:#{@status.id}", autorelease: 5.minutes.seconds }
end
diff --git a/app/views/admin/action_logs/index.html.haml b/app/views/admin/action_logs/index.html.haml
index 347eca166af..03d5bffb94d 100644
--- a/app/views/admin/action_logs/index.html.haml
+++ b/app/views/admin/action_logs/index.html.haml
@@ -19,7 +19,7 @@
%div.muted-hint.center-text
= t 'admin.action_logs.empty'
- else
- .announcements-list
+ .report-notes
= render partial: 'action_log', collection: @action_logs
= paginate @action_logs
diff --git a/app/views/admin/report_notes/_report_note.html.haml b/app/views/admin/report_notes/_report_note.html.haml
index d34dc3d1575..428b6cf59c6 100644
--- a/app/views/admin/report_notes/_report_note.html.haml
+++ b/app/views/admin/report_notes/_report_note.html.haml
@@ -1,7 +1,18 @@
-.speech-bubble
- .speech-bubble__bubble
+.report-notes__item
+ = 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))
- .speech-bubble__owner
- = admin_account_link_to report_note.account
- %time.formatted{ datetime: report_note.created_at.iso8601 }= l report_note.created_at
- = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete if can?(:destroy, report_note)
+
+ - if can?(:destroy, report_note)
+ .report-notes__item__actions
+ = table_link_to 'trash', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete
diff --git a/app/views/admin/reports/_action_log.html.haml b/app/views/admin/reports/_action_log.html.haml
deleted file mode 100644
index 0f7d0586792..00000000000
--- a/app/views/admin/reports/_action_log.html.haml
+++ /dev/null
@@ -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
diff --git a/app/views/admin/reports/_status.html.haml b/app/views/admin/reports/_status.html.haml
index ada6dd2bc54..924b0e9c255 100644
--- a/app/views/admin/reports/_status.html.haml
+++ b/app/views/admin/reports/_status.html.haml
@@ -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 }
.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
%time.formatted{ datetime: status.created_at.iso8601, title: l(status.created_at) }= l(status.created_at)
- if status.discarded?
diff --git a/app/views/admin/reports/show.html.haml b/app/views/admin/reports/show.html.haml
index 167e96c0397..e03c1220ccd 100644
--- a/app/views/admin/reports/show.html.haml
+++ b/app/views/admin/reports/show.html.haml
@@ -7,122 +7,199 @@
- else
= link_to t('admin.reports.mark_as_unresolved'), reopen_admin_report_path(@report), method: :post, class: 'button'
-.table-wrapper
- %table.table.inline-table
- %tbody
- %tr
- %th= t('admin.reports.reported_account')
- %td= admin_account_link_to @report.target_account
- %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)
- %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)
- %tr
- %th= t('admin.reports.reported_by')
+.report-header
+ .report-header__card
+ .account-card
+ .account-card__header
+ = image_tag @report.target_account.header.url, alt: ''
+ .account-card__title
+ .account-card__title__avatar
+ = image_tag @report.target_account.avatar.url, alt: ''
+ .display-name
+ %bdi
+ %strong.emojify.p-name= display_name(@report.target_account, custom_emojify: true)
+ %span
+ = acct(@report.target_account)
+ = fa_icon('lock') if @report.target_account.locked?
+ - if @report.target_account.note.present?
+ .account-card__bio.emojify
+ = Formatter.instance.simplified_format(@report.target_account, custom_emojify: true)
+ .account-card__actions
+ .account-card__counters
+ .account-card__counters__item
+ = 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 }
+ .report-header__details__item
+ .report-header__details__item__header
+ %strong= t('admin.reports.reported_by')
+ .report-header__details__item__content
- if @report.account.instance_actor?
- %td{ colspan: 3 }= site_hostname
+ = site_hostname
- elsif @report.account.local?
- %td= admin_account_link_to @report.account
- %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)
- %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)
+ = admin_account_link_to @report.account
- else
- %td{ colspan: 3 }= @report.account.domain
- %tr
- %th= t('admin.reports.created_at')
- %td{ colspan: 3 }
- %time.formatted{ datetime: @report.created_at.iso8601 }
- %tr
- %th= t('admin.reports.updated_at')
- %td{ colspan: 3 }
- %time.formatted{ datetime: @report.updated_at.iso8601 }
- %tr
- %th= t('admin.reports.status')
- %td
- - if @report.action_taken?
- = t('admin.reports.resolved')
+ = @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?
+ = t('admin.reports.resolved')
+ - else
+ = t('admin.reports.unresolved')
+ - unless @report.target_account.local?
+ .report-header__details__item
+ .report-header__details__item__header
+ %strong= t('admin.reports.forwarded')
+ .report-header__details__item__content
+ - if @report.forwarded?
+ = t('simple_form.yes')
- else
- = 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?
- %tr
- %th= t('admin.reports.forwarded')
- %td{ colspan: 3 }
- - if @report.forwarded.nil?
- \-
- - elsif @report.forwarded?
- = t('simple_form.yes')
- - else
- = t('simple_form.no')
- - if !@report.action_taken_by_account.nil?
- %tr
- %th= t('admin.reports.action_taken_by')
- %td{ colspan: 3 }
- = admin_account_link_to @report.action_taken_by_account
- - else
- %tr
- %th= t('admin.reports.assigned')
- %td
- - if @report.assigned_account.nil?
- \-
- - else
- = admin_account_link_to @report.assigned_account
- %td
- - 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
- %td
- - if !@report.assigned_account.nil?
- = table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post
-
-%hr.spacer
-
-%div.action-buttons
- %div
-
- - if @report.unresolved?
- %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
-
-.speech-bubble
- .speech-bubble__bubble= simple_format(@report.comment.presence || t('admin.reports.comment.none'))
- .speech-bubble__owner
- - if @report.account.local?
- = admin_account_link_to @report.account
+ = t('simple_form.no')
+ - if !@report.action_taken_by_account.nil?
+ .report-header__details__item
+ .report-header__details__item__header
+ %strong= t('admin.reports.action_taken_by')
+ .report-header__details__item__content
+ = admin_account_link_to @report.action_taken_by_account
- else
- = @report.account.domain
- %br/
- %time.formatted{ datetime: @report.created_at.iso8601 }
+ .report-header__details__item
+ .report-header__details__item__header
+ %strong= t('admin.reports.assigned')
+ .report-header__details__item__content
+ - if @report.assigned_account.nil?
+ = t 'admin.reports.no_one_assigned'
+ - else
+ = admin_account_link_to @report.assigned_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
+ - elsif !@report.assigned_account.nil?
+ = table_link_to 'trash', t('admin.reports.unassign'), unassign_admin_report_path(@report), method: :post
-- unless @report.statuses.empty?
- %hr.spacer/
+%hr.spacer
- = form_for(@form, url: admin_report_reported_statuses_path(@report.id)) do |f|
- .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: @report.statuses, locals: { f: f }
+%h3= t 'admin.reports.category'
+
+%p= t 'admin.reports.category_description_html'
+
+= react_admin_component :report_reason_selector, id: @report.id, category: @report.category, rule_ids: @report.rule_ids&.map(&:to_s), disabled: @report.action_taken?
+
+- if @report.comment.present?
+ %p= t('admin.reports.comment_description_html', name: content_tag(:strong, @report.account.username, class: 'username'))
+
+ .report-notes__item
+ = 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
+ = l @report.created_at.to_date
+
+ .report-notes__item__content
+ = simple_format(h(@report.comment))
%hr.spacer/
-- @report_notes.each do |item|
- - if item.is_a?(Admin::ActionLog)
- = render partial: 'action_log', locals: { action_log: item }
- - else
- = render item
+%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__toolbar
+ %label.batch-table__toolbar__select.batch-checkbox-all
+ = check_box_tag :batch_checkbox_all, nil, false
+ .batch-table__toolbar__actions
+ - if !@statuses.empty? && @report.unresolved?
+ = 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.reports.delete_and_resolve')]), name: :delete, class: 'table-action-link', type: :submit, data: { confirm: t('admin.reports.are_you_sure') }
+ - else
+ .batch-table__body
+ - 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/
+
+%h3= t 'admin.reports.notes.title'
+
+%p= t 'admin.reports.notes_description_html'
+
+.report-notes
+ = render @report_notes
= 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
.field-group
diff --git a/app/views/admin/statuses/index.html.haml b/app/views/admin/statuses/index.html.haml
index 5414d69d504..865464c72c3 100644
--- a/app/views/admin/statuses/index.html.haml
+++ b/app/views/admin/statuses/index.html.haml
@@ -7,28 +7,37 @@
.filter-subset
%strong= t('admin.statuses.media.title')
%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= 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('generic.all'), media: nil, id: nil
+ %li= filter_link_to t('admin.statuses.with_media'), media: '1'
.back-link
- = link_to admin_account_path(@account.id) do
- = fa_icon 'chevron-left fw'
- = t('admin.statuses.back_to_account')
+ - 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
+ = fa_icon 'chevron-left 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]
+= form_for(@status_batch_action, url: batch_admin_account_statuses_path(@account.id)) do |f|
+ = hidden_field_tag :page, params[:page] || 1
+
+ - Admin::StatusFilter::KEYS.each do |key|
+ = hidden_field_tag key, params[key] if params[key].present?
.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') }
+ - unless @statuses.empty?
+ = 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') }
.batch-table__body
- = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
+ - if @statuses.empty?
+ = nothing_here 'nothing-here--under-tabs'
+ - else
+ = render partial: 'admin/reports/status', collection: @statuses, locals: { f: f }
= paginate @statuses
diff --git a/app/views/admin/statuses/show.html.haml b/app/views/admin/statuses/show.html.haml
deleted file mode 100644
index e2470198d66..00000000000
--- a/app/views/admin/statuses/show.html.haml
+++ /dev/null
@@ -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 }
diff --git a/app/views/notification_mailer/_status.text.erb b/app/views/notification_mailer/_status.text.erb
index 8999a1f8ea5..c43f32d9f33 100644
--- a/app/views/notification_mailer/_status.text.erb
+++ b/app/views/notification_mailer/_status.text.erb
@@ -1,8 +1,8 @@
<% if status.spoiler_text? %>
-<%= raw status.spoiler_text %>
-----
-
+> <%= raw word_wrap(status.spoiler_text, break_sequence: "\n> ") %>
+> ----
+>
<% 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}") %>
diff --git a/app/views/user_mailer/warning.html.haml b/app/views/user_mailer/warning.html.haml
index 5a2911ecba5..bda1fef6cfa 100644
--- a/app/views/user_mailer/warning.html.haml
+++ b/app/views/user_mailer/warning.html.haml
@@ -37,16 +37,26 @@
%tr
%td.column-cell.text-center
- 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?
= 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
%strong= t('user_mailer.warning.statuses')
-- if !@statuses.nil? && !@statuses.empty?
+- unless @statuses.empty?
- @statuses.each_with_index do |status, i|
= render 'notification_mailer/status', status: status, i: i + 1, highlighted: true
diff --git a/app/views/user_mailer/warning.text.erb b/app/views/user_mailer/warning.text.erb
index bb6610c79bd..31d7308aea5 100644
--- a/app/views/user_mailer/warning.text.erb
+++ b/app/views/user_mailer/warning.text.erb
@@ -3,11 +3,24 @@
===
<% unless @warning.none_action? %>
-<%= t "user_mailer.warning.explanation.#{@warning.action}" %>
+<%= t "user_mailer.warning.explanation.#{@warning.action}", instance: @instance %>
<% end %>
+<% if @warning.text.present? %>
<%= @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') %>
<% @statuses.each do |status| %>
diff --git a/app/workers/scheduler/user_cleanup_scheduler.rb b/app/workers/scheduler/user_cleanup_scheduler.rb
index be0c4277dab..d06b637f916 100644
--- a/app/workers/scheduler/user_cleanup_scheduler.rb
+++ b/app/workers/scheduler/user_cleanup_scheduler.rb
@@ -8,6 +8,7 @@ class Scheduler::UserCleanupScheduler
def perform
clean_unconfirmed_accounts!
clean_suspended_accounts!
+ clean_discarded_statuses!
end
private
@@ -24,4 +25,12 @@ class Scheduler::UserCleanupScheduler
Admin::AccountDeletionWorker.perform_async(deletion_request.account_id)
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
diff --git a/config/locales/en.yml b/config/locales/en.yml
index 693a7b40084..36ac896643f 100644
--- a/config/locales/en.yml
+++ b/config/locales/en.yml
@@ -113,6 +113,7 @@ en:
confirm: Confirm
confirmed: Confirmed
confirming: Confirming
+ custom: Custom
delete: Delete data
deleted: Deleted
demote: Demote
@@ -203,6 +204,7 @@ en:
silence: Limit
silenced: Limited
statuses: Posts
+ strikes: Previous strikes
subscribe: Subscribe
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.
@@ -549,32 +551,44 @@ en:
report_notes:
created_msg: Report note successfully created!
destroyed_msg: Report note successfully deleted!
+ today_at: Today at %{time}
reports:
account:
notes:
one: "%{count} note"
other: "%{count} notes"
- reports:
- one: "%{count} report"
- other: "%{count} reports"
+ action_log: Audit log
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?
assign_to_self: Assign to me
assigned: Assigned moderator
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:
none: None
+ comment_description_html: 'To provide more information, %{name} wrote:'
created_at: Reported
+ delete_and_resolve: Delete and resolve
forwarded: Forwarded
forwarded_to: Forwarded to %{domain}
mark_as_resolved: Mark as resolved
mark_as_unresolved: Mark as unresolved
+ no_one_assigned: No one
notes:
create: Add note
create_and_resolve: Resolve with note
create_and_unresolve: Reopen with note
delete: Delete
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
report: 'Report #%{id}'
reported_account: Reported account
@@ -582,11 +596,14 @@ en:
resolved: Resolved
resolved_msg: Report successfully resolved!
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
title: Reports
unassign: Unassign
unresolved: Unresolved
updated_at: Updated
+ view_profile: View profile
rules:
add_new: Add rule
delete: Delete
@@ -688,15 +705,13 @@ en:
destroyed_msg: Site upload successfully deleted!
statuses:
back_to_account: Back to account page
+ back_to_report: Back to report page
batch:
- delete: Delete
- nsfw_off: Mark as not sensitive
- nsfw_on: Mark as sensitive
+ remove_from_report: Remove from report
+ report: Report
deleted: Deleted
- failed_to_execute: Failed to execute
media:
title: Media
- no_media: No media
no_status_selected: No posts were changed as none were selected
title: Account posts
with_media: With media
@@ -1457,6 +1472,7 @@ en:
formats:
default: "%b %d, %Y, %H:%M"
month: "%b %Y"
+ time: "%H:%M"
two_factor_authentication:
add: Add
disable: Disable 2FA
@@ -1484,24 +1500,31 @@ en:
subject: Please confirm attempted sign in
title: Sign in attempt
warning:
+ categories:
+ spam: Spam
+ violation: Content violates the following community guidelines
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.
- sensitive: Your uploaded media files and linked media will be treated as sensitive.
- 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.
- 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.
- get_in_touch: You can reply to this e-mail to get in touch with the staff of %{instance}.
+ 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.
+ 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.
+ sensitive: From now on, all your uploaded media files will be marked as sensitive and hidden behind a click-through warning.
+ 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.
+ 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
- statuses: 'Specifically, for:'
+ statuses: 'Posts that have been found in violation:'
subject:
+ delete_statuses: Your posts on %{acct} have been removed
disable: Your account %{acct} has been frozen
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
suspend: Your account %{acct} has been suspended
title:
+ delete_statuses: Posts removed
disable: Account frozen
none: Warning
- sensitive: Your media has been marked as sensitive
+ sensitive: Media hidden
silence: Account limited
suspend: Account suspended
welcome:
diff --git a/config/routes.rb b/config/routes.rb
index 285a1cdc93c..f071b93797a 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -233,8 +233,6 @@ Rails.application.routes.draw do
post :reopen
post :resolve
end
-
- resources :reported_statuses, only: [:create]
end
resources :report_notes, only: [:create, :destroy]
@@ -261,7 +259,13 @@ Rails.application.routes.draw do
resource :change_email, only: [:show, :update]
resource :reset, only: [:create]
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]
resource :confirmation, only: [:create] do
@@ -518,7 +522,7 @@ Rails.application.routes.draw do
resource :action, only: [:create], controller: 'account_actions'
end
- resources :reports, only: [:index, :show] do
+ resources :reports, only: [:index, :update, :show] do
member do
post :assign_to_self
post :unassign
diff --git a/db/migrate/20211231080958_add_category_to_reports.rb b/db/migrate/20211231080958_add_category_to_reports.rb
new file mode 100644
index 00000000000..c2b495c6354
--- /dev/null
+++ b/db/migrate/20211231080958_add_category_to_reports.rb
@@ -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
diff --git a/db/migrate/20220115125126_add_report_id_to_account_warnings.rb b/db/migrate/20220115125126_add_report_id_to_account_warnings.rb
new file mode 100644
index 00000000000..a1c20c99ef8
--- /dev/null
+++ b/db/migrate/20220115125126_add_report_id_to_account_warnings.rb
@@ -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
diff --git a/db/migrate/20220115125341_fix_account_warning_actions.rb b/db/migrate/20220115125341_fix_account_warning_actions.rb
new file mode 100644
index 00000000000..25cc17fd392
--- /dev/null
+++ b/db/migrate/20220115125341_fix_account_warning_actions.rb
@@ -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
diff --git a/db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb b/db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb
new file mode 100644
index 00000000000..dc336255281
--- /dev/null
+++ b/db/migrate/20220116202951_add_deleted_at_index_on_statuses.rb
@@ -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
diff --git a/db/post_migrate/20220109213908_remove_action_taken_from_reports.rb b/db/post_migrate/20220109213908_remove_action_taken_from_reports.rb
new file mode 100644
index 00000000000..73e6ad6f43f
--- /dev/null
+++ b/db/post_migrate/20220109213908_remove_action_taken_from_reports.rb
@@ -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
diff --git a/db/schema.rb b/db/schema.rb
index d357512b3e9..7b5a301ffe7 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -10,7 +10,7 @@
#
# 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
enable_extension "plpgsql"
@@ -133,6 +133,8 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
t.text "text", default: "", null: false
t.datetime "created_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 ["target_account_id"], name: "index_account_warnings_on_target_account_id"
end
@@ -747,7 +749,6 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
create_table "reports", force: :cascade do |t|
t.bigint "status_ids", default: [], null: false, array: true
t.text "comment", default: "", null: false
- t.boolean "action_taken", default: false, null: false
t.datetime "created_at", null: false
t.datetime "updated_at", 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.string "uri"
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 ["target_account_id"], name: "index_reports_on_target_account_id"
end
@@ -853,6 +857,7 @@ ActiveRecord::Schema.define(version: 2021_12_13_040746) do
t.string "content_type"
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 ["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_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"
@@ -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_warnings", "accounts", column: "target_account_id", on_delete: :cascade
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 "admin_action_logs", "accounts", on_delete: :cascade
add_foreign_key "announcement_mutes", "accounts", on_delete: :cascade
diff --git a/spec/controllers/admin/report_notes_controller_spec.rb b/spec/controllers/admin/report_notes_controller_spec.rb
index ec5872c7d2a..c0013f41aec 100644
--- a/spec/controllers/admin/report_notes_controller_spec.rb
+++ b/spec/controllers/admin/report_notes_controller_spec.rb
@@ -12,11 +12,11 @@ describe Admin::ReportNotesController do
describe 'POST #create' do
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 report is unsolved' do
- let(:action_taken) { false }
+ let(:action_taken) { nil }
let(:account_id) { nil }
context 'when create_and_resolve flag is on' do
@@ -41,7 +41,7 @@ describe Admin::ReportNotesController do
end
context 'when report is resolved' do
- let(:action_taken) { true }
+ let(:action_taken) { Time.now.utc }
let(:account_id) { user.account.id }
context 'when create_and_unresolve flag is on' do
@@ -68,7 +68,7 @@ describe Admin::ReportNotesController do
context 'when parameter is invalid' do
let(:params) { { report_note: { content: '', report_id: report.id } } }
- let(:action_taken) { false }
+ let(:action_taken) { nil }
let(:account_id) { nil }
it 'renders admin/reports/show' do
diff --git a/spec/controllers/admin/reported_statuses_controller_spec.rb b/spec/controllers/admin/reported_statuses_controller_spec.rb
deleted file mode 100644
index 2a1598123c1..00000000000
--- a/spec/controllers/admin/reported_statuses_controller_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/controllers/admin/reports_controller_spec.rb b/spec/controllers/admin/reports_controller_spec.rb
index 49d3e970743..d421f0739c9 100644
--- a/spec/controllers/admin/reports_controller_spec.rb
+++ b/spec/controllers/admin/reports_controller_spec.rb
@@ -10,8 +10,8 @@ describe Admin::ReportsController do
describe 'GET #index' do
it 'returns http success with no filters' do
- specified = Fabricate(:report, action_taken: false)
- Fabricate(:report, action_taken: true)
+ specified = Fabricate(:report, action_taken_at: nil)
+ Fabricate(:report, action_taken_at: Time.now.utc)
get :index
@@ -22,10 +22,10 @@ describe Admin::ReportsController do
end
it 'returns http success with resolved filter' do
- specified = Fabricate(:report, action_taken: true)
- Fabricate(:report, action_taken: false)
+ specified = Fabricate(:report, action_taken_at: Time.now.utc)
+ Fabricate(:report, action_taken_at: nil)
- get :index, params: { resolved: 1 }
+ get :index, params: { resolved: '1' }
reports = assigns(:reports).to_a
expect(reports.size).to eq 1
@@ -54,15 +54,7 @@ describe Admin::ReportsController do
expect(response).to redirect_to(admin_reports_path)
report.reload
expect(report.action_taken_by_account).to eq user.account
- 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]
+ expect(report.action_taken?).to eq true
end
end
@@ -74,7 +66,7 @@ describe Admin::ReportsController do
expect(response).to redirect_to(admin_report_path(report))
report.reload
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
diff --git a/spec/controllers/admin/statuses_controller_spec.rb b/spec/controllers/admin/statuses_controller_spec.rb
index e388caae205..de32fd18e1d 100644
--- a/spec/controllers/admin/statuses_controller_spec.rb
+++ b/spec/controllers/admin/statuses_controller_spec.rb
@@ -18,65 +18,46 @@ describe Admin::StatusesController do
end
describe 'GET #index' do
- it 'returns http success with no media' do
- get :index, params: { account_id: account.id }
+ context do
+ before do
+ get :index, params: { account_id: account.id }
+ end
- 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)
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
end
- it 'returns http success with media' do
- get :index, params: { account_id: account.id, media: true }
+ context 'filtering by media' do
+ before do
+ get :index, params: { account_id: account.id, media: '1' }
+ end
- 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)
+ it 'returns http success' do
+ expect(response).to have_http_status(200)
+ end
end
end
- describe 'POST #create' do
- subject do
- -> { post :create, params: { :account_id => account.id, action => '', :form_status_batch => { status_ids: status_ids } } }
+ describe 'POST #batch' do
+ before do
+ post :batch, params: { :account_id => account.id, action => '', :admin_status_batch_action => { status_ids: status_ids } }
end
- let(:action) { 'nsfw_on' }
let(:status_ids) { [media_attached_status.id] }
- context 'when action is nsfw_on' do
- it 'updates sensitive column' do
- is_expected.to change {
- media_attached_status.reload.sensitive
- }.from(false).to(true)
+ context 'when action is report' do
+ let(:action) { 'report' }
+
+ it 'creates a report' do
+ report = Report.last
+ 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
- let(:action) { 'nsfw_off' }
- let(:sensitive) { false }
-
- it 'updates sensitive column' do
- is_expected.to change {
- media_attached_status.reload.sensitive
- }.from(true).to(false)
+ it 'redirects to report page' do
+ expect(response).to redirect_to(admin_report_path(Report.last.id))
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
diff --git a/spec/fabricators/report_fabricator.rb b/spec/fabricators/report_fabricator.rb
index 5bd4a63f024..2c7101e094a 100644
--- a/spec/fabricators/report_fabricator.rb
+++ b/spec/fabricators/report_fabricator.rb
@@ -1,6 +1,6 @@
Fabricator(:report) do
account
- target_account { Fabricate(:account) }
- comment "You nasty"
- action_taken false
+ target_account { Fabricate(:account) }
+ comment "You nasty"
+ action_taken_at nil
end
diff --git a/spec/mailers/previews/user_mailer_preview.rb b/spec/mailers/previews/user_mailer_preview.rb
index 6d87fd706e0..69b9b971eec 100644
--- a/spec/mailers/previews/user_mailer_preview.rb
+++ b/spec/mailers/previews/user_mailer_preview.rb
@@ -79,7 +79,7 @@ class UserMailerPreview < ActionMailer::Preview
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/warning
def warning
- UserMailer.warning(User.first, AccountWarning.new(text: '', action: :silence), [Status.first.id])
+ UserMailer.warning(User.first, AccountWarning.last)
end
# Preview this email at http://localhost:3000/rails/mailers/user_mailer/sign_in_token
diff --git a/spec/models/form/status_batch_spec.rb b/spec/models/form/status_batch_spec.rb
deleted file mode 100644
index 68d84a7379b..00000000000
--- a/spec/models/form/status_batch_spec.rb
+++ /dev/null
@@ -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
diff --git a/spec/models/report_spec.rb b/spec/models/report_spec.rb
index 312954c9dc9..3d29c021954 100644
--- a/spec/models/report_spec.rb
+++ b/spec/models/report_spec.rb
@@ -54,7 +54,7 @@ describe Report do
end
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) }
@@ -63,12 +63,13 @@ describe Report do
end
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
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) }
@@ -77,23 +78,24 @@ describe Report do
end
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
describe 'unresolved?' do
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
- let(:action_taken) { true }
+ let(:action_taken) { Time.now.utc }
it { is_expected.to be false }
end
context 'if action not is taken' do
- let(:action_taken) { false }
+ let(:action_taken) { nil }
it { is_expected.to be true }
end