Allow reporting (local) Collections (#37848)

This commit is contained in:
David Roetzel
2026-02-13 12:11:44 +01:00
committed by GitHub
parent a3f34137fd
commit 6c3bd944f8
9 changed files with 132 additions and 22 deletions

View File

@@ -23,6 +23,10 @@ class Api::V1::ReportsController < Api::BaseController
end
def report_params
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: [])
if Mastodon::Feature.collections_enabled?
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], collection_ids: [], rule_ids: [])
else
params.permit(:account_id, :comment, :category, :forward, forward_to_domains: [], status_ids: [], rule_ids: [])
end
end
end

View File

@@ -26,6 +26,7 @@ class Collection < ApplicationRecord
belongs_to :tag, optional: true
has_many :collection_items, dependent: :delete_all
has_many :collection_reports, dependent: :delete_all
validates :name, presence: true
validates :description, presence: true

View File

@@ -0,0 +1,16 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: collection_reports
#
# id :bigint(8) not null, primary key
# created_at :datetime not null
# updated_at :datetime not null
# collection_id :bigint(8) not null
# report_id :bigint(8) not null
#
class CollectionReport < ApplicationRecord
belongs_to :collection
belongs_to :report
end

View File

@@ -40,6 +40,8 @@ class Report < ApplicationRecord
belongs_to :assigned_account, optional: true
end
has_many :collection_reports, dependent: :delete_all
has_many :collections, through: :collection_reports
has_many :notes, class_name: 'ReportNote', inverse_of: :report, dependent: :destroy
has_many :notifications, as: :activity, dependent: :destroy

View File

@@ -2,7 +2,8 @@
class REST::ReportSerializer < ActiveModel::Serializer
attributes :id, :action_taken, :action_taken_at, :category, :comment,
:forwarded, :created_at, :status_ids, :rule_ids
:forwarded, :created_at, :status_ids, :rule_ids,
:collection_ids
has_one :target_account, serializer: REST::AccountSerializer
@@ -17,4 +18,8 @@ class REST::ReportSerializer < ActiveModel::Serializer
def rule_ids
object&.rule_ids&.map(&:to_s)
end
def collection_ids
object.collection_ids.map(&:to_s)
end
end

View File

@@ -7,6 +7,7 @@ class ReportService < BaseService
@source_account = source_account
@target_account = target_account
@status_ids = options.delete(:status_ids).presence || []
@collection_ids = options.delete(:collection_ids).presence || []
@comment = options.delete(:comment).presence || ''
@category = options[:rule_ids].present? ? 'violation' : (options.delete(:category).presence || 'other')
@rule_ids = options.delete(:rule_ids).presence
@@ -32,6 +33,7 @@ class ReportService < BaseService
@report = @source_account.reports.create!(
target_account: @target_account,
status_ids: reported_status_ids,
collection_ids: reported_collection_ids,
comment: @comment,
uri: @options[:uri],
forwarded: forward_to_origin?,
@@ -91,6 +93,10 @@ class ReportService < BaseService
scope.where(id: Array(@status_ids)).pluck(:id)
end
def reported_collection_ids
@target_account.collections.find(Array(@collection_ids)).pluck(:id)
end
def payload
Oj.dump(serialize_payload(@report, ActivityPub::FlagSerializer, account: some_local_account))
end

View File

@@ -0,0 +1,12 @@
# frozen_string_literal: true
class CreateCollectionReports < ActiveRecord::Migration[8.0]
def change
create_table :collection_reports do |t|
t.references :collection, null: false, foreign_key: { on_delete: :cascade }
t.references :report, null: false, foreign_key: { on_delete: :cascade }
t.timestamps
end
end
end

View File

@@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema[8.0].define(version: 2026_02_12_113020) do
ActiveRecord::Schema[8.0].define(version: 2026_02_12_131934) do
# These are extensions that must be enabled in order to support this database
enable_extension "pg_catalog.plpgsql"
@@ -372,6 +372,15 @@ ActiveRecord::Schema[8.0].define(version: 2026_02_12_113020) do
t.index ["object_uri"], name: "index_collection_items_on_object_uri", unique: true, where: "(activity_uri IS NOT NULL)"
end
create_table "collection_reports", force: :cascade do |t|
t.bigint "collection_id", null: false
t.bigint "report_id", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
t.index ["collection_id"], name: "index_collection_reports_on_collection_id"
t.index ["report_id"], name: "index_collection_reports_on_report_id"
end
create_table "collections", id: :bigint, default: -> { "timestamp_id('collections'::text)" }, force: :cascade do |t|
t.bigint "account_id", null: false
t.string "name", null: false
@@ -1431,6 +1440,8 @@ ActiveRecord::Schema[8.0].define(version: 2026_02_12_113020) do
add_foreign_key "canonical_email_blocks", "accounts", column: "reference_account_id", on_delete: :cascade
add_foreign_key "collection_items", "accounts"
add_foreign_key "collection_items", "collections", on_delete: :cascade
add_foreign_key "collection_reports", "collections", on_delete: :cascade
add_foreign_key "collection_reports", "reports", on_delete: :cascade
add_foreign_key "collections", "accounts"
add_foreign_key "collections", "tags"
add_foreign_key "conversation_mutes", "accounts", name: "fk_225b4212bb", on_delete: :cascade

View File

@@ -11,20 +11,22 @@ RSpec.describe 'Reports' do
end
let!(:admin) { Fabricate(:admin_user) }
let(:status) { Fabricate(:status) }
let(:target_account) { status.account }
let(:target_account) { Fabricate(:account) }
let(:category) { 'other' }
let(:forward) { nil }
let(:rule_ids) { nil }
let(:status_ids) { nil }
let(:collection_ids) { nil }
let(:params) do
{
status_ids: [status.id],
status_ids:,
collection_ids:,
account_id: target_account.id,
comment: 'reasons',
category: category,
rule_ids: rule_ids,
forward: forward,
category:,
rule_ids:,
forward:,
}
end
@@ -38,7 +40,6 @@ RSpec.describe 'Reports' do
.to start_with('application/json')
expect(response.parsed_body).to match(
a_hash_including(
status_ids: [status.id.to_s],
category: category,
comment: 'reasons'
)
@@ -57,18 +58,6 @@ RSpec.describe 'Reports' do
)
end
context 'when a status does not belong to the reported account' do
let(:target_account) { Fabricate(:account) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
expect(response.content_type)
.to start_with('application/json')
end
end
context 'when a category is chosen' do
let(:category) { 'spam' }
@@ -91,5 +80,69 @@ RSpec.describe 'Reports' do
expect(target_account.targeted_reports.first.rule_ids).to contain_exactly(rule.id)
end
end
context 'with attached status' do
let(:status) { Fabricate(:status, account: target_account) }
let(:status_ids) { [status.id] }
it 'creates a report including the status ids', :aggregate_failures, :inline_jobs do
subject
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body).to match(
a_hash_including(
status_ids: [status.id.to_s],
category: category,
comment: 'reasons'
)
)
end
context 'when a status does not belong to the reported account' do
let(:status) { Fabricate(:status) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
expect(response.content_type)
.to start_with('application/json')
end
end
end
context 'with attached collection', feature: :collections do
let(:collection) { Fabricate(:collection, account: target_account) }
let(:collection_ids) { [collection.id] }
it 'creates a report including the collection ids', :aggregate_failures, :inline_jobs do
subject
expect(response).to have_http_status(200)
expect(response.content_type)
.to start_with('application/json')
expect(response.parsed_body).to match(
a_hash_including(
collection_ids: [collection.id.to_s],
category: category,
comment: 'reasons'
)
)
end
context 'when a collection does not belong to the reported account' do
let(:collection) { Fabricate(:collection) }
it 'returns http not found' do
subject
expect(response).to have_http_status(404)
expect(response.content_type)
.to start_with('application/json')
end
end
end
end
end