Implement Instance Moderation Notes (#31529)
This commit is contained in:
		@@ -0,0 +1,44 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class Admin::Instances::ModerationNotesController < Admin::BaseController
 | 
				
			||||||
 | 
					  before_action :set_instance, only: [:create]
 | 
				
			||||||
 | 
					  before_action :set_instance_note, only: [:destroy]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def create
 | 
				
			||||||
 | 
					    authorize :instance_moderation_note, :create?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @instance_moderation_note = current_account.instance_moderation_notes.new(content: resource_params[:content], domain: @instance.domain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    if @instance_moderation_note.save
 | 
				
			||||||
 | 
					      redirect_to admin_instance_path(@instance.domain, anchor: helpers.dom_id(@instance_moderation_note)), notice: I18n.t('admin.instances.moderation_notes.created_msg')
 | 
				
			||||||
 | 
					    else
 | 
				
			||||||
 | 
					      @instance_moderation_notes = @instance.moderation_notes.includes(:account).chronological
 | 
				
			||||||
 | 
					      @time_period = (6.days.ago.to_date...Time.now.utc.to_date)
 | 
				
			||||||
 | 
					      @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(5)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      render 'admin/instances/show'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy
 | 
				
			||||||
 | 
					    authorize @instance_moderation_note, :destroy?
 | 
				
			||||||
 | 
					    @instance_moderation_note.destroy!
 | 
				
			||||||
 | 
					    redirect_to admin_instance_path(@instance_moderation_note.domain, anchor: 'instance-notes'), notice: I18n.t('admin.instances.moderation_notes.destroyed_msg')
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def resource_params
 | 
				
			||||||
 | 
					    params
 | 
				
			||||||
 | 
					      .expect(instance_moderation_note: [:content])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_instance
 | 
				
			||||||
 | 
					    domain = params[:instance_id]&.strip
 | 
				
			||||||
 | 
					    @instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def set_instance_note
 | 
				
			||||||
 | 
					    @instance_moderation_note = InstanceModerationNote.find(params[:id])
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -14,6 +14,9 @@ module Admin
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
    def show
 | 
					    def show
 | 
				
			||||||
      authorize :instance, :show?
 | 
					      authorize :instance, :show?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      @instance_moderation_note = @instance.moderation_notes.new
 | 
				
			||||||
 | 
					      @instance_moderation_notes = @instance.moderation_notes.includes(:account).chronological
 | 
				
			||||||
      @time_period = (6.days.ago.to_date...Time.now.utc.to_date)
 | 
					      @time_period = (6.days.ago.to_date...Time.now.utc.to_date)
 | 
				
			||||||
      @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
 | 
					      @action_logs = Admin::ActionLogFilter.new(target_domain: @instance.domain).results.limit(LOGS_LIMIT)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
@@ -52,7 +55,8 @@ module Admin
 | 
				
			|||||||
    private
 | 
					    private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_instance
 | 
					    def set_instance
 | 
				
			||||||
      @instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(params[:id]&.strip))
 | 
					      domain = params[:id]&.strip
 | 
				
			||||||
 | 
					      @instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain(domain))
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def set_instances
 | 
					    def set_instances
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1632,6 +1632,17 @@ a.sparkline {
 | 
				
			|||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      a.timestamp {
 | 
				
			||||||
 | 
					        color: $darker-text-color;
 | 
				
			||||||
 | 
					        text-decoration: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        &:hover,
 | 
				
			||||||
 | 
					        &:focus,
 | 
				
			||||||
 | 
					        &:active {
 | 
				
			||||||
 | 
					          text-decoration: underline;
 | 
				
			||||||
 | 
					        }
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      time {
 | 
					      time {
 | 
				
			||||||
        margin-inline-start: 5px;
 | 
					        margin-inline-start: 5px;
 | 
				
			||||||
        vertical-align: baseline;
 | 
					        vertical-align: baseline;
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -18,6 +18,7 @@ module Account::Associations
 | 
				
			|||||||
        has_many :favourites
 | 
					        has_many :favourites
 | 
				
			||||||
        has_many :featured_tags, -> { includes(:tag) }
 | 
					        has_many :featured_tags, -> { includes(:tag) }
 | 
				
			||||||
        has_many :list_accounts
 | 
					        has_many :list_accounts
 | 
				
			||||||
 | 
					        has_many :instance_moderation_notes
 | 
				
			||||||
        has_many :media_attachments
 | 
					        has_many :media_attachments
 | 
				
			||||||
        has_many :mentions
 | 
					        has_many :mentions
 | 
				
			||||||
        has_many :migrations, class_name: 'AccountMigration'
 | 
					        has_many :migrations, class_name: 'AccountMigration'
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -21,6 +21,7 @@ class Instance < ApplicationRecord
 | 
				
			|||||||
    belongs_to :unavailable_domain
 | 
					    belongs_to :unavailable_domain
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    has_many :accounts, dependent: nil
 | 
					    has_many :accounts, dependent: nil
 | 
				
			||||||
 | 
					    has_many :moderation_notes, class_name: 'InstanceModerationNote', dependent: :destroy
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  scope :searchable, -> { where.not(domain: DomainBlock.select(:domain)) }
 | 
					  scope :searchable, -> { where.not(domain: DomainBlock.select(:domain)) }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										27
									
								
								app/models/instance_moderation_note.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										27
									
								
								app/models/instance_moderation_note.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,27 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					# == Schema Information
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					# Table name: instance_moderation_notes
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					#  id         :bigint(8)        not null, primary key
 | 
				
			||||||
 | 
					#  content    :text
 | 
				
			||||||
 | 
					#  domain     :string           not null
 | 
				
			||||||
 | 
					#  created_at :datetime         not null
 | 
				
			||||||
 | 
					#  updated_at :datetime         not null
 | 
				
			||||||
 | 
					#  account_id :bigint(8)        not null
 | 
				
			||||||
 | 
					#
 | 
				
			||||||
 | 
					class InstanceModerationNote < ApplicationRecord
 | 
				
			||||||
 | 
					  include DomainNormalizable
 | 
				
			||||||
 | 
					  include DomainMaterializable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  CONTENT_SIZE_LIMIT = 2_000
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  belongs_to :account
 | 
				
			||||||
 | 
					  belongs_to :instance, inverse_of: :moderation_notes, foreign_key: :domain, primary_key: :domain, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  scope :chronological, -> { reorder(id: :asc) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  validates :content, presence: true, length: { maximum: CONTENT_SIZE_LIMIT }
 | 
				
			||||||
 | 
					  validates :domain, presence: true, domain: true
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										17
									
								
								app/policies/instance_moderation_note_policy.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								app/policies/instance_moderation_note_policy.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,17 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class InstanceModerationNotePolicy < ApplicationPolicy
 | 
				
			||||||
 | 
					  def create?
 | 
				
			||||||
 | 
					    role.can?(:manage_federation)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def destroy?
 | 
				
			||||||
 | 
					    owner? || (role.can?(:manage_federation) && role.overrides?(record.account.user_role))
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def owner?
 | 
				
			||||||
 | 
					    record.account_id == current_account&.id
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -6,7 +6,7 @@
 | 
				
			|||||||
    = date_range(@time_period)
 | 
					    = date_range(@time_period)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  - if @instance.persisted?
 | 
					  - if @instance.persisted?
 | 
				
			||||||
    = render 'dashboard', instance_domain: @instance.domain, period_end_at: @time_period.last, period_start_at: @time_period.first
 | 
					    = render 'admin/instances/dashboard', instance_domain: @instance.domain, period_end_at: @time_period.last, period_start_at: @time_period.first
 | 
				
			||||||
  - else
 | 
					  - else
 | 
				
			||||||
    %p
 | 
					    %p
 | 
				
			||||||
      = t('admin.instances.unknown_instance')
 | 
					      = t('admin.instances.unknown_instance')
 | 
				
			||||||
@@ -57,6 +57,24 @@
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
%hr.spacer/
 | 
					%hr.spacer/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- if @instance.domain.present?
 | 
				
			||||||
 | 
					  %h3#instance-notes= t('admin.instances.moderation_notes.title')
 | 
				
			||||||
 | 
					  %p= t('admin.instances.moderation_notes.description_html')
 | 
				
			||||||
 | 
					  .report-notes
 | 
				
			||||||
 | 
					    = render partial: 'admin/report_notes/report_note', collection: @instance_moderation_notes
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  = simple_form_for @instance_moderation_note, url: admin_instance_moderation_notes_path(instance_id: @instance.domain) do |form|
 | 
				
			||||||
 | 
					    = render 'shared/error_messages', object: @instance_moderation_note
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .field-group
 | 
				
			||||||
 | 
					      = form.input :content, input_html: { placeholder: t('admin.instances.moderation_notes.placeholder'), maxlength: InstanceModerationNote::CONTENT_SIZE_LIMIT, rows: 6, autofocus: @instance_moderation_note.errors.any? }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .actions
 | 
				
			||||||
 | 
					      = form.button :button, t('admin.instances.moderation_notes.create'), type: :submit
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					- if @instance.persisted?
 | 
				
			||||||
 | 
					  %hr.spacer/
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  %h3= t('admin.instances.availability.title')
 | 
					  %h3= t('admin.instances.availability.title')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  %p
 | 
					  %p
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -1,9 +1,10 @@
 | 
				
			|||||||
.report-notes__item
 | 
					.report-notes__item{ id: dom_id(report_note) }
 | 
				
			||||||
  = image_tag report_note.account.avatar.url, class: 'report-notes__item__avatar'
 | 
					  = image_tag report_note.account.avatar.url, class: 'report-notes__item__avatar'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .report-notes__item__header
 | 
					  .report-notes__item__header
 | 
				
			||||||
    %span.username
 | 
					    %span.username
 | 
				
			||||||
      = link_to report_note.account.username, admin_account_path(report_note.account_id)
 | 
					      = link_to report_note.account.username, admin_account_path(report_note.account_id)
 | 
				
			||||||
 | 
					    %a.timestamp{ href: "##{dom_id(report_note)}" }
 | 
				
			||||||
      %time.relative-formatted{ datetime: report_note.created_at.iso8601, title: report_note.created_at }
 | 
					      %time.relative-formatted{ datetime: report_note.created_at.iso8601, title: report_note.created_at }
 | 
				
			||||||
        = l report_note.created_at.to_date
 | 
					        = l report_note.created_at.to_date
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,5 +15,7 @@
 | 
				
			|||||||
    .report-notes__item__actions
 | 
					    .report-notes__item__actions
 | 
				
			||||||
      - if report_note.is_a?(AccountModerationNote)
 | 
					      - if report_note.is_a?(AccountModerationNote)
 | 
				
			||||||
        = table_link_to 'delete', t('admin.reports.notes.delete'), admin_account_moderation_note_path(report_note), method: :delete
 | 
					        = table_link_to 'delete', t('admin.reports.notes.delete'), admin_account_moderation_note_path(report_note), method: :delete
 | 
				
			||||||
 | 
					      - elsif report_note.is_a?(InstanceModerationNote)
 | 
				
			||||||
 | 
					        = table_link_to 'delete', t('admin.reports.notes.delete'), admin_instance_moderation_note_path(instance_id: report_note.domain, id: report_note.id), method: :delete
 | 
				
			||||||
      - else
 | 
					      - else
 | 
				
			||||||
        = table_link_to 'delete', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete
 | 
					        = table_link_to 'delete', t('admin.reports.notes.delete'), admin_report_note_path(report_note), method: :delete
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -578,6 +578,13 @@ en:
 | 
				
			|||||||
        all: All
 | 
					        all: All
 | 
				
			||||||
        limited: Limited
 | 
					        limited: Limited
 | 
				
			||||||
        title: Moderation
 | 
					        title: Moderation
 | 
				
			||||||
 | 
					      moderation_notes:
 | 
				
			||||||
 | 
					        create: Add Moderation Note
 | 
				
			||||||
 | 
					        created_msg: Instance moderation note successfully created!
 | 
				
			||||||
 | 
					        description_html: View and leave notes for other moderators and your future self
 | 
				
			||||||
 | 
					        destroyed_msg: Instance moderation note successfully deleted!
 | 
				
			||||||
 | 
					        placeholder: Information about this instance, actions taken, or anything else that will help you moderate this instance in the future.
 | 
				
			||||||
 | 
					        title: Moderation Notes
 | 
				
			||||||
      private_comment: Private comment
 | 
					      private_comment: Private comment
 | 
				
			||||||
      public_comment: Public comment
 | 
					      public_comment: Public comment
 | 
				
			||||||
      purge: Purge
 | 
					      purge: Purge
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -91,6 +91,8 @@ namespace :admin do
 | 
				
			|||||||
      post :restart_delivery
 | 
					      post :restart_delivery
 | 
				
			||||||
      post :stop_delivery
 | 
					      post :stop_delivery
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    resources :moderation_notes, controller: 'instances/moderation_notes', only: [:create, :destroy]
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  resources :rules, only: [:index, :new, :create, :edit, :update, :destroy] do
 | 
					  resources :rules, only: [:index, :new, :create, :edit, :update, :destroy] do
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -0,0 +1,15 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					class CreateInstanceModerationNotes < ActiveRecord::Migration[8.0]
 | 
				
			||||||
 | 
					  def change
 | 
				
			||||||
 | 
					    create_table :instance_moderation_notes do |t|
 | 
				
			||||||
 | 
					      t.string :domain, null: false
 | 
				
			||||||
 | 
					      t.belongs_to :account, foreign_key: { on_delete: :cascade }, index: false, null: false
 | 
				
			||||||
 | 
					      t.text :content
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      t.timestamps
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      t.index ['domain'], name: 'index_instance_moderation_notes_on_domain'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										20
									
								
								db/schema.rb
									
									
									
									
									
								
							
							
						
						
									
										20
									
								
								db/schema.rb
									
									
									
									
									
								
							@@ -191,8 +191,8 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_05_110215) do
 | 
				
			|||||||
    t.boolean "hide_collections"
 | 
					    t.boolean "hide_collections"
 | 
				
			||||||
    t.integer "avatar_storage_schema_version"
 | 
					    t.integer "avatar_storage_schema_version"
 | 
				
			||||||
    t.integer "header_storage_schema_version"
 | 
					    t.integer "header_storage_schema_version"
 | 
				
			||||||
    t.datetime "sensitized_at", precision: nil
 | 
					 | 
				
			||||||
    t.integer "suspension_origin"
 | 
					    t.integer "suspension_origin"
 | 
				
			||||||
 | 
					    t.datetime "sensitized_at", precision: nil
 | 
				
			||||||
    t.boolean "trendable"
 | 
					    t.boolean "trendable"
 | 
				
			||||||
    t.datetime "reviewed_at", precision: nil
 | 
					    t.datetime "reviewed_at", precision: nil
 | 
				
			||||||
    t.datetime "requested_review_at", precision: nil
 | 
					    t.datetime "requested_review_at", precision: nil
 | 
				
			||||||
@@ -580,6 +580,15 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_05_110215) do
 | 
				
			|||||||
    t.index ["user_id"], name: "index_identities_on_user_id"
 | 
					    t.index ["user_id"], name: "index_identities_on_user_id"
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  create_table "instance_moderation_notes", force: :cascade do |t|
 | 
				
			||||||
 | 
					    t.string "domain", null: false
 | 
				
			||||||
 | 
					    t.bigint "account_id", null: false
 | 
				
			||||||
 | 
					    t.text "content"
 | 
				
			||||||
 | 
					    t.datetime "created_at", null: false
 | 
				
			||||||
 | 
					    t.datetime "updated_at", null: false
 | 
				
			||||||
 | 
					    t.index ["domain"], name: "index_instance_moderation_notes_on_domain"
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create_table "invites", force: :cascade do |t|
 | 
					  create_table "invites", force: :cascade do |t|
 | 
				
			||||||
    t.bigint "user_id", null: false
 | 
					    t.bigint "user_id", null: false
 | 
				
			||||||
    t.string "code", default: "", null: false
 | 
					    t.string "code", default: "", null: false
 | 
				
			||||||
@@ -595,12 +604,12 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_05_110215) do
 | 
				
			|||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  create_table "ip_blocks", force: :cascade do |t|
 | 
					  create_table "ip_blocks", force: :cascade do |t|
 | 
				
			||||||
    t.inet "ip", default: "0.0.0.0", null: false
 | 
					 | 
				
			||||||
    t.integer "severity", default: 0, null: false
 | 
					 | 
				
			||||||
    t.datetime "expires_at", precision: nil
 | 
					 | 
				
			||||||
    t.text "comment", default: "", null: false
 | 
					 | 
				
			||||||
    t.datetime "created_at", precision: nil, null: false
 | 
					    t.datetime "created_at", precision: nil, null: false
 | 
				
			||||||
    t.datetime "updated_at", precision: nil, null: false
 | 
					    t.datetime "updated_at", precision: nil, null: false
 | 
				
			||||||
 | 
					    t.datetime "expires_at", precision: nil
 | 
				
			||||||
 | 
					    t.inet "ip", default: "0.0.0.0", null: false
 | 
				
			||||||
 | 
					    t.integer "severity", default: 0, null: false
 | 
				
			||||||
 | 
					    t.text "comment", default: "", null: false
 | 
				
			||||||
    t.index ["ip"], name: "index_ip_blocks_on_ip", unique: true
 | 
					    t.index ["ip"], name: "index_ip_blocks_on_ip", unique: true
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -1372,6 +1381,7 @@ ActiveRecord::Schema[8.0].define(version: 2025_06_05_110215) do
 | 
				
			|||||||
  add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
 | 
					  add_foreign_key "follows", "accounts", name: "fk_32ed1b5560", on_delete: :cascade
 | 
				
			||||||
  add_foreign_key "generated_annual_reports", "accounts"
 | 
					  add_foreign_key "generated_annual_reports", "accounts"
 | 
				
			||||||
  add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
 | 
					  add_foreign_key "identities", "users", name: "fk_bea040f377", on_delete: :cascade
 | 
				
			||||||
 | 
					  add_foreign_key "instance_moderation_notes", "accounts", on_delete: :cascade
 | 
				
			||||||
  add_foreign_key "invites", "users", on_delete: :cascade
 | 
					  add_foreign_key "invites", "users", on_delete: :cascade
 | 
				
			||||||
  add_foreign_key "list_accounts", "accounts", on_delete: :cascade
 | 
					  add_foreign_key "list_accounts", "accounts", on_delete: :cascade
 | 
				
			||||||
  add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade
 | 
					  add_foreign_key "list_accounts", "follow_requests", on_delete: :cascade
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										7
									
								
								spec/fabricators/instance_moderation_note_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								spec/fabricators/instance_moderation_note_fabricator.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,7 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					Fabricator(:instance_moderation_note) do
 | 
				
			||||||
 | 
					  domain { sequence(:domain) { |i| "#{i}#{Faker::Internet.domain_name}" } }
 | 
				
			||||||
 | 
					  account { Fabricate.build(:account) }
 | 
				
			||||||
 | 
					  content { Faker::Lorem.sentence }
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										37
									
								
								spec/models/instance_moderation_note_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										37
									
								
								spec/models/instance_moderation_note_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,37 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe InstanceModerationNote do
 | 
				
			||||||
 | 
					  describe 'chronological' do
 | 
				
			||||||
 | 
					    it 'returns the instance notes sorted by oldest first' do
 | 
				
			||||||
 | 
					      instance = Instance.find_or_initialize_by(domain: TagManager.instance.normalize_domain('mastodon.example'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      note1 = Fabricate(:instance_moderation_note, domain: instance.domain)
 | 
				
			||||||
 | 
					      note2 = Fabricate(:instance_moderation_note, domain: instance.domain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(instance.moderation_notes.chronological).to eq [note1, note2]
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'validations' do
 | 
				
			||||||
 | 
					    it 'is invalid if the content is empty' do
 | 
				
			||||||
 | 
					      note = Fabricate.build(:instance_moderation_note, domain: 'mastodon.example', content: '')
 | 
				
			||||||
 | 
					      expect(note.valid?).to be false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'is invalid if content is longer than character limit' do
 | 
				
			||||||
 | 
					      note = Fabricate.build(:instance_moderation_note, domain: 'mastodon.example', content: comment_over_limit)
 | 
				
			||||||
 | 
					      expect(note.valid?).to be false
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'is valid even if the instance does not exist yet' do
 | 
				
			||||||
 | 
					      note = Fabricate.build(:instance_moderation_note, domain: 'non-existent.example', content: 'test comment')
 | 
				
			||||||
 | 
					      expect(note.valid?).to be true
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def comment_over_limit
 | 
				
			||||||
 | 
					      Faker::Lorem.paragraph_by_chars(number: described_class::CONTENT_SIZE_LIMIT * 2)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -3,9 +3,9 @@
 | 
				
			|||||||
require 'rails_helper'
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RSpec.describe Instance do
 | 
					RSpec.describe Instance do
 | 
				
			||||||
  describe 'Scopes' do
 | 
					 | 
				
			||||||
  before { described_class.refresh }
 | 
					  before { described_class.refresh }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'Scopes' do
 | 
				
			||||||
    describe '#searchable' do
 | 
					    describe '#searchable' do
 | 
				
			||||||
      let(:expected_domain) { 'host.example' }
 | 
					      let(:expected_domain) { 'host.example' }
 | 
				
			||||||
      let(:blocked_domain) { 'other.example' }
 | 
					      let(:blocked_domain) { 'other.example' }
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										16
									
								
								spec/requests/admin/instances/moderation_notes_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								spec/requests/admin/instances/moderation_notes_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,16 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe 'Admin Report Notes' do
 | 
				
			||||||
 | 
					  describe 'POST /admin/instance/moderation_notes' do
 | 
				
			||||||
 | 
					    before { sign_in Fabricate(:admin_user) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'gracefully handles invalid nested params' do
 | 
				
			||||||
 | 
					      post admin_instance_moderation_notes_path(instance_id: 'mastodon.test', instance_note: 'invalid')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(response)
 | 
				
			||||||
 | 
					        .to have_http_status(400)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
@@ -4,9 +4,9 @@ require 'rails_helper'
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
RSpec.describe 'Admin Instances' do
 | 
					RSpec.describe 'Admin Instances' do
 | 
				
			||||||
  describe 'GET /admin/instances/:id' do
 | 
					  describe 'GET /admin/instances/:id' do
 | 
				
			||||||
    context 'with an unknown domain' do
 | 
					 | 
				
			||||||
    before { sign_in Fabricate(:admin_user) }
 | 
					    before { sign_in Fabricate(:admin_user) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'with an unknown domain' do
 | 
				
			||||||
      it 'returns http success' do
 | 
					      it 'returns http success' do
 | 
				
			||||||
        get admin_instance_path(id: 'unknown.example')
 | 
					        get admin_instance_path(id: 'unknown.example')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
@@ -14,5 +14,14 @@ RSpec.describe 'Admin Instances' do
 | 
				
			|||||||
          .to have_http_status(200)
 | 
					          .to have_http_status(200)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'with an invalid domain' do
 | 
				
			||||||
 | 
					      it 'returns http success' do
 | 
				
			||||||
 | 
					        get admin_instance_path(id: ' ')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        expect(response)
 | 
				
			||||||
 | 
					          .to have_http_status(200)
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -35,7 +35,7 @@ RSpec.describe 'Admin::AccountModerationNotes' do
 | 
				
			|||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def delete_note
 | 
					    def delete_note
 | 
				
			||||||
      within('.report-notes__item__actions') do
 | 
					      within('.report-notes__item:first-child .report-notes__item__actions') do
 | 
				
			||||||
        click_on I18n.t('admin.reports.notes.delete')
 | 
					        click_on I18n.t('admin.reports.notes.delete')
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 
 | 
				
			|||||||
							
								
								
									
										51
									
								
								spec/system/admin/instance/moderation_notes_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								spec/system/admin/instance/moderation_notes_spec.rb
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,51 @@
 | 
				
			|||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe 'Admin::Instances::ModerationNotesController' do
 | 
				
			||||||
 | 
					  let(:current_user) { Fabricate(:admin_user) }
 | 
				
			||||||
 | 
					  let(:instance_domain) { 'mastodon.example' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before { sign_in current_user }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe 'Managing instance moderation notes' do
 | 
				
			||||||
 | 
					    it 'saves and then deletes a record' do
 | 
				
			||||||
 | 
					      visit admin_instance_path(instance_domain)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fill_in 'instance_moderation_note_content', with: ''
 | 
				
			||||||
 | 
					      expect { submit_form }
 | 
				
			||||||
 | 
					        .to not_change(InstanceModerationNote, :count)
 | 
				
			||||||
 | 
					      expect(page)
 | 
				
			||||||
 | 
					        .to have_content(/error below/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fill_in 'instance_moderation_note_content', with: 'Test message ' * InstanceModerationNote::CONTENT_SIZE_LIMIT
 | 
				
			||||||
 | 
					      expect { submit_form }
 | 
				
			||||||
 | 
					        .to not_change(InstanceModerationNote, :count)
 | 
				
			||||||
 | 
					      expect(page)
 | 
				
			||||||
 | 
					        .to have_content(/error below/)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      fill_in 'instance_moderation_note_content', with: 'Test message'
 | 
				
			||||||
 | 
					      expect { submit_form }
 | 
				
			||||||
 | 
					        .to change(InstanceModerationNote, :count).by(1)
 | 
				
			||||||
 | 
					      expect(page)
 | 
				
			||||||
 | 
					        .to have_current_path(admin_instance_path(instance_domain))
 | 
				
			||||||
 | 
					      expect(page)
 | 
				
			||||||
 | 
					        .to have_content(I18n.t('admin.instances.moderation_notes.created_msg'))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect { delete_note }
 | 
				
			||||||
 | 
					        .to change(InstanceModerationNote, :count).by(-1)
 | 
				
			||||||
 | 
					      expect(page)
 | 
				
			||||||
 | 
					        .to have_content(I18n.t('admin.instances.moderation_notes.destroyed_msg'))
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def submit_form
 | 
				
			||||||
 | 
					      click_on I18n.t('admin.instances.moderation_notes.create')
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def delete_note
 | 
				
			||||||
 | 
					      within('.report-notes__item:first-child .report-notes__item__actions') do
 | 
				
			||||||
 | 
					        click_on I18n.t('admin.reports.notes.delete')
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Reference in New Issue
	
	Block a user