forked from treehouse/mastodon
Merge branch 'main' into glitch-soc/merge-upstream
Conflicts: - `db/schema.rb`: Conflict due to glitch-soc adding the `content_type` column on status edits and thus having a different schema version number. Solved by taking upstream's schema version number, as it is higher than glitch-soc's.signup-info-prompt
commit
f224237862
|
@ -32,7 +32,7 @@ commands:
|
|||
name: Install system dependencies
|
||||
command: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
install-ruby-dependencies:
|
||||
parameters:
|
||||
ruby-version:
|
||||
|
|
|
@ -6,6 +6,10 @@ on:
|
|||
- "main"
|
||||
tags:
|
||||
- "*"
|
||||
pull_request:
|
||||
paths:
|
||||
- .github/workflows/build-image.yml
|
||||
- Dockerfile
|
||||
jobs:
|
||||
build-image:
|
||||
runs-on: ubuntu-latest
|
||||
|
@ -31,7 +35,7 @@ jobs:
|
|||
with:
|
||||
context: .
|
||||
platforms: linux/amd64,linux/arm64
|
||||
push: true
|
||||
push: ${{ github.event_name != 'pull_request' }}
|
||||
tags: ${{ steps.meta.outputs.tags }}
|
||||
cache-from: type=registry,ref=ghcr.io/${{ github.repository_owner }}/mastodon:latest
|
||||
cache-to: type=inline
|
||||
|
|
|
@ -18,7 +18,7 @@ jobs:
|
|||
- name: Install system dependencies
|
||||
run: |
|
||||
sudo apt-get update
|
||||
sudo apt-get install -y libicu-dev libidn11-dev libprotobuf-dev protobuf-compiler
|
||||
sudo apt-get install -y libicu-dev libidn11-dev
|
||||
- name: Set up Ruby
|
||||
uses: ruby/setup-ruby@v1
|
||||
with:
|
||||
|
|
2
Aptfile
2
Aptfile
|
@ -4,10 +4,8 @@ libicu-dev
|
|||
libidn11
|
||||
libidn11-dev
|
||||
libpq-dev
|
||||
libprotobuf-dev
|
||||
libxdamage1
|
||||
libxfixes3
|
||||
protobuf-compiler
|
||||
zlib1g-dev
|
||||
libcairo2
|
||||
libcroco3
|
||||
|
|
|
@ -51,7 +51,7 @@ RUN npm install -g npm@latest && \
|
|||
gem install bundler && \
|
||||
apt-get update && \
|
||||
apt-get install -y --no-install-recommends git libicu-dev libidn11-dev \
|
||||
libpq-dev libprotobuf-dev protobuf-compiler shared-mime-info
|
||||
libpq-dev shared-mime-info
|
||||
|
||||
COPY Gemfile* package.json yarn.lock /opt/mastodon/
|
||||
|
||||
|
@ -88,7 +88,7 @@ RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selectio
|
|||
RUN apt-get update && \
|
||||
apt-get -y --no-install-recommends install \
|
||||
libssl1.1 libpq5 imagemagick ffmpeg libjemalloc2 \
|
||||
libicu66 libprotobuf17 libidn11 libyaml-0-2 \
|
||||
libicu66 libidn11 libyaml-0-2 \
|
||||
file ca-certificates tzdata libreadline8 gcc tini apt-utils && \
|
||||
ln -s /opt/mastodon /mastodon && \
|
||||
gem install bundler && \
|
||||
|
|
8
Gemfile
8
Gemfile
|
@ -21,7 +21,7 @@ gem 'dotenv-rails', '~> 2.7'
|
|||
gem 'aws-sdk-s3', '~> 1.112', require: false
|
||||
gem 'fog-core', '<= 2.1.0'
|
||||
gem 'fog-openstack', '~> 0.3', require: false
|
||||
gem 'kt-paperclip', '~> 7.0'
|
||||
gem 'kt-paperclip', '~> 7.1'
|
||||
gem 'blurhash', '~> 0.1'
|
||||
|
||||
gem 'active_model_serializers', '~> 0.10'
|
||||
|
@ -76,7 +76,7 @@ gem 'mario-redis-lock', '~> 1.2', require: 'redis_lock'
|
|||
gem 'rqrcode', '~> 2.1'
|
||||
gem 'ruby-progressbar', '~> 1.11'
|
||||
gem 'sanitize', '~> 6.0'
|
||||
gem 'scenic', '~> 1.5'
|
||||
gem 'scenic', '~> 1.6'
|
||||
gem 'sidekiq', '~> 6.4'
|
||||
gem 'sidekiq-scheduler', '~> 3.1'
|
||||
gem 'sidekiq-unique-jobs', '~> 7.1'
|
||||
|
@ -105,7 +105,7 @@ group :development, :test do
|
|||
gem 'i18n-tasks', '~> 0.9', require: false
|
||||
gem 'pry-byebug', '~> 3.9'
|
||||
gem 'pry-rails', '~> 0.3'
|
||||
gem 'rspec-rails', '~> 5.0'
|
||||
gem 'rspec-rails', '~> 5.1'
|
||||
end
|
||||
|
||||
group :production, :test do
|
||||
|
@ -126,7 +126,7 @@ end
|
|||
|
||||
group :development do
|
||||
gem 'active_record_query_trace', '~> 1.8'
|
||||
gem 'annotate', '~> 3.1'
|
||||
gem 'annotate', '~> 3.2'
|
||||
gem 'better_errors', '~> 2.9'
|
||||
gem 'binding_of_caller', '~> 1.0'
|
||||
gem 'bullet', '~> 7.0'
|
||||
|
|
150
Gemfile.lock
150
Gemfile.lock
|
@ -1,40 +1,40 @@
|
|||
GEM
|
||||
remote: https://rubygems.org/
|
||||
specs:
|
||||
actioncable (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actioncable (6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
nio4r (~> 2.0)
|
||||
websocket-driver (>= 0.6.1)
|
||||
actionmailbox (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activestorage (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionmailbox (6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
activejob (= 6.1.4.6)
|
||||
activerecord (= 6.1.4.6)
|
||||
activestorage (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
mail (>= 2.7.1)
|
||||
actionmailer (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
actionview (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionmailer (6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
actionview (= 6.1.4.6)
|
||||
activejob (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
mail (~> 2.5, >= 2.5.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
actionpack (6.1.4.4)
|
||||
actionview (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionpack (6.1.4.6)
|
||||
actionview (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
rack (~> 2.0, >= 2.0.9)
|
||||
rack-test (>= 0.6.3)
|
||||
rails-dom-testing (~> 2.0)
|
||||
rails-html-sanitizer (~> 1.0, >= 1.2.0)
|
||||
actiontext (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activestorage (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actiontext (6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
activerecord (= 6.1.4.6)
|
||||
activestorage (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
nokogiri (>= 1.8.5)
|
||||
actionview (6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
actionview (6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
builder (~> 3.1)
|
||||
erubi (~> 1.4)
|
||||
rails-dom-testing (~> 2.0)
|
||||
|
@ -45,22 +45,22 @@ GEM
|
|||
case_transform (>= 0.2)
|
||||
jsonapi-renderer (>= 0.1.1.beta1, < 0.3)
|
||||
active_record_query_trace (1.8)
|
||||
activejob (6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activejob (6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
globalid (>= 0.3.6)
|
||||
activemodel (6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activerecord (6.1.4.4)
|
||||
activemodel (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activestorage (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
activemodel (6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
activerecord (6.1.4.6)
|
||||
activemodel (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
activestorage (6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
activejob (= 6.1.4.6)
|
||||
activerecord (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
marcel (~> 1.0.0)
|
||||
mini_mime (>= 1.1.0)
|
||||
activesupport (6.1.4.4)
|
||||
activesupport (6.1.4.6)
|
||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||
i18n (>= 1.6, < 2)
|
||||
minitest (>= 5.1)
|
||||
|
@ -71,8 +71,8 @@ GEM
|
|||
airbrussh (1.4.0)
|
||||
sshkit (>= 1.6.1, != 1.7.0)
|
||||
android_key_attestation (0.3.0)
|
||||
annotate (3.1.1)
|
||||
activerecord (>= 3.2, < 7.0)
|
||||
annotate (3.2.0)
|
||||
activerecord (>= 3.2, < 8.0)
|
||||
rake (>= 10.4, < 14.0)
|
||||
ast (2.4.2)
|
||||
attr_encrypted (3.1.0)
|
||||
|
@ -181,7 +181,7 @@ GEM
|
|||
devise_pam_authenticatable2 (9.2.0)
|
||||
devise (>= 4.0.0)
|
||||
rpam2 (~> 4.0)
|
||||
diff-lcs (1.4.4)
|
||||
diff-lcs (1.5.0)
|
||||
discard (1.2.1)
|
||||
activerecord (>= 4.2, < 8)
|
||||
docile (1.3.4)
|
||||
|
@ -332,7 +332,7 @@ GEM
|
|||
activerecord
|
||||
kaminari-core (= 1.2.2)
|
||||
kaminari-core (1.2.2)
|
||||
kt-paperclip (7.0.1)
|
||||
kt-paperclip (7.1.1)
|
||||
activemodel (>= 4.2.0)
|
||||
activesupport (>= 4.2.0)
|
||||
marcel (~> 1.0.1)
|
||||
|
@ -356,14 +356,14 @@ GEM
|
|||
activesupport (>= 4)
|
||||
railties (>= 4)
|
||||
request_store (~> 1.0)
|
||||
loofah (2.13.0)
|
||||
loofah (2.14.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.5.9)
|
||||
mail (2.7.1)
|
||||
mini_mime (>= 0.1.1)
|
||||
makara (0.5.1)
|
||||
activerecord (>= 5.2.0)
|
||||
marcel (1.0.1)
|
||||
marcel (1.0.2)
|
||||
mario-redis-lock (1.2.1)
|
||||
redis (>= 3.0.5)
|
||||
matrix (0.4.2)
|
||||
|
@ -374,7 +374,7 @@ GEM
|
|||
nokogiri (~> 1.10)
|
||||
mime-types (3.4.1)
|
||||
mime-types-data (~> 3.2015)
|
||||
mime-types-data (3.2021.1115)
|
||||
mime-types-data (3.2022.0105)
|
||||
mini_mime (1.1.2)
|
||||
mini_portile2 (2.7.1)
|
||||
minitest (5.15.0)
|
||||
|
@ -418,7 +418,7 @@ GEM
|
|||
parslet (2.0.0)
|
||||
pastel (0.8.0)
|
||||
tty-color (~> 0.5)
|
||||
pg (1.3.1)
|
||||
pg (1.3.2)
|
||||
pghero (2.8.2)
|
||||
activerecord (>= 5)
|
||||
pkg-config (1.4.7)
|
||||
|
@ -455,20 +455,20 @@ GEM
|
|||
rack
|
||||
rack-test (1.1.0)
|
||||
rack (>= 1.0, < 3)
|
||||
rails (6.1.4.4)
|
||||
actioncable (= 6.1.4.4)
|
||||
actionmailbox (= 6.1.4.4)
|
||||
actionmailer (= 6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
actiontext (= 6.1.4.4)
|
||||
actionview (= 6.1.4.4)
|
||||
activejob (= 6.1.4.4)
|
||||
activemodel (= 6.1.4.4)
|
||||
activerecord (= 6.1.4.4)
|
||||
activestorage (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
rails (6.1.4.6)
|
||||
actioncable (= 6.1.4.6)
|
||||
actionmailbox (= 6.1.4.6)
|
||||
actionmailer (= 6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
actiontext (= 6.1.4.6)
|
||||
actionview (= 6.1.4.6)
|
||||
activejob (= 6.1.4.6)
|
||||
activemodel (= 6.1.4.6)
|
||||
activerecord (= 6.1.4.6)
|
||||
activestorage (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
bundler (>= 1.15.0)
|
||||
railties (= 6.1.4.4)
|
||||
railties (= 6.1.4.6)
|
||||
sprockets-rails (>= 2.0.0)
|
||||
rails-controller-testing (1.0.5)
|
||||
actionpack (>= 5.0.1.rc1)
|
||||
|
@ -484,9 +484,9 @@ GEM
|
|||
railties (>= 6.0.0, < 7)
|
||||
rails-settings-cached (0.6.6)
|
||||
rails (>= 4.2.0)
|
||||
railties (6.1.4.4)
|
||||
actionpack (= 6.1.4.4)
|
||||
activesupport (= 6.1.4.4)
|
||||
railties (6.1.4.6)
|
||||
actionpack (= 6.1.4.6)
|
||||
activesupport (= 6.1.4.6)
|
||||
method_source
|
||||
rake (>= 0.13)
|
||||
thor (~> 1.0)
|
||||
|
@ -509,19 +509,19 @@ GEM
|
|||
rexml (3.2.5)
|
||||
rotp (6.2.0)
|
||||
rpam2 (4.0.2)
|
||||
rqrcode (2.1.0)
|
||||
rqrcode (2.1.1)
|
||||
chunky_png (~> 1.0)
|
||||
rqrcode_core (~> 1.0)
|
||||
rqrcode_core (1.2.0)
|
||||
rspec-core (3.10.1)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-expectations (3.10.1)
|
||||
rspec-core (3.11.0)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-expectations (3.11.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-mocks (3.10.2)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-mocks (3.11.0)
|
||||
diff-lcs (>= 1.2.0, < 2.0)
|
||||
rspec-support (~> 3.10.0)
|
||||
rspec-rails (5.0.2)
|
||||
rspec-support (~> 3.11.0)
|
||||
rspec-rails (5.1.0)
|
||||
actionpack (>= 5.2)
|
||||
activesupport (>= 5.2)
|
||||
railties (>= 5.2)
|
||||
|
@ -532,7 +532,7 @@ GEM
|
|||
rspec-sidekiq (3.1.0)
|
||||
rspec-core (~> 3.0, >= 3.0.0)
|
||||
sidekiq (>= 2.4.0)
|
||||
rspec-support (3.10.3)
|
||||
rspec-support (3.11.0)
|
||||
rspec_junit_formatter (0.5.1)
|
||||
rspec-core (>= 2, < 4, != 2.12.0)
|
||||
rubocop (1.25.1)
|
||||
|
@ -562,7 +562,7 @@ GEM
|
|||
sanitize (6.0.0)
|
||||
crass (~> 1.0.2)
|
||||
nokogiri (>= 1.12.0)
|
||||
scenic (1.5.5)
|
||||
scenic (1.6.0)
|
||||
activerecord (>= 4.0.0)
|
||||
railties (>= 4.0.0)
|
||||
securecompare (1.0.0)
|
||||
|
@ -685,7 +685,7 @@ DEPENDENCIES
|
|||
active_model_serializers (~> 0.10)
|
||||
active_record_query_trace (~> 1.8)
|
||||
addressable (~> 2.8)
|
||||
annotate (~> 3.1)
|
||||
annotate (~> 3.2)
|
||||
aws-sdk-s3 (~> 1.112)
|
||||
better_errors (~> 2.9)
|
||||
binding_of_caller (~> 1.0)
|
||||
|
@ -732,7 +732,7 @@ DEPENDENCIES
|
|||
json-ld
|
||||
json-ld-preloaded (~> 3.2)
|
||||
kaminari (~> 1.2)
|
||||
kt-paperclip (~> 7.0)
|
||||
kt-paperclip (~> 7.1)
|
||||
letter_opener (~> 1.7)
|
||||
letter_opener_web (~> 2.0)
|
||||
link_header (~> 0.0)
|
||||
|
@ -775,14 +775,14 @@ DEPENDENCIES
|
|||
redis-namespace (~> 1.8)
|
||||
rexml (~> 3.2)
|
||||
rqrcode (~> 2.1)
|
||||
rspec-rails (~> 5.0)
|
||||
rspec-rails (~> 5.1)
|
||||
rspec-sidekiq (~> 3.1)
|
||||
rspec_junit_formatter (~> 0.5)
|
||||
rubocop (~> 1.25)
|
||||
rubocop-rails (~> 2.13)
|
||||
ruby-progressbar (~> 1.11)
|
||||
sanitize (~> 6.0)
|
||||
scenic (~> 1.5)
|
||||
scenic (~> 1.6)
|
||||
sidekiq (~> 6.4)
|
||||
sidekiq-bulk (~> 0.2.0)
|
||||
sidekiq-scheduler (~> 3.1)
|
||||
|
|
|
@ -33,11 +33,9 @@ sudo apt-get install \
|
|||
redis-tools \
|
||||
postgresql \
|
||||
postgresql-contrib \
|
||||
protobuf-compiler \
|
||||
yarn \
|
||||
libicu-dev \
|
||||
libidn11-dev \
|
||||
libprotobuf-dev \
|
||||
libreadline-dev \
|
||||
libpam0g-dev \
|
||||
-y
|
||||
|
|
|
@ -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.strikes.custom.latest
|
||||
@warnings = @account.strikes.includes(:target_account, :account, :appeal).latest
|
||||
@domain_block = DomainBlock.rule_for(@account.domain)
|
||||
end
|
||||
|
||||
|
@ -146,7 +146,7 @@ module Admin
|
|||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(*AccountFilter::KEYS).permit(*AccountFilter::KEYS)
|
||||
params.slice(:page, *AccountFilter::KEYS).permit(:page, *AccountFilter::KEYS)
|
||||
end
|
||||
|
||||
def form_account_batch_params
|
||||
|
|
|
@ -8,6 +8,7 @@ module Admin
|
|||
@pending_users_count = User.pending.count
|
||||
@pending_reports_count = Report.unresolved.count
|
||||
@pending_tags_count = Tag.pending_review.count
|
||||
@pending_appeals_count = Appeal.pending.count
|
||||
end
|
||||
|
||||
private
|
||||
|
|
|
@ -0,0 +1,40 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::Disputes::AppealsController < Admin::BaseController
|
||||
before_action :set_appeal, except: :index
|
||||
|
||||
def index
|
||||
authorize :appeal, :index?
|
||||
|
||||
@appeals = filtered_appeals.page(params[:page])
|
||||
end
|
||||
|
||||
def approve
|
||||
authorize @appeal, :approve?
|
||||
log_action :approve, @appeal
|
||||
ApproveAppealService.new.call(@appeal, current_account)
|
||||
redirect_to disputes_strike_path(@appeal.strike)
|
||||
end
|
||||
|
||||
def reject
|
||||
authorize @appeal, :approve?
|
||||
log_action :reject, @appeal
|
||||
@appeal.reject!(current_account)
|
||||
UserMailer.appeal_rejected(@appeal.account.user, @appeal)
|
||||
redirect_to disputes_strike_path(@appeal.strike)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def filtered_appeals
|
||||
Admin::AppealFilter.new(filter_params.with_defaults(status: 'pending')).results.includes(strike: :account)
|
||||
end
|
||||
|
||||
def filter_params
|
||||
params.slice(:page, *Admin::AppealFilter::KEYS).permit(:page, *Admin::AppealFilter::KEYS)
|
||||
end
|
||||
|
||||
def set_appeal
|
||||
@appeal = Appeal.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -10,6 +10,7 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
before_action :configure_sign_up_params, only: [:create]
|
||||
before_action :set_pack
|
||||
before_action :set_sessions, only: [:edit, :update]
|
||||
before_action :set_strikes, only: [:edit, :update]
|
||||
before_action :set_instance_presenter, only: [:new, :create, :update]
|
||||
before_action :set_body_classes, only: [:new, :create, :edit, :update]
|
||||
before_action :require_not_suspended!, only: [:update]
|
||||
|
@ -116,8 +117,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
end
|
||||
|
||||
def set_invite
|
||||
invite = invite_code.present? ? Invite.find_by(code: invite_code) : nil
|
||||
@invite = invite&.valid_for_use? ? invite : nil
|
||||
@invite = begin
|
||||
invite = Invite.find_by(code: invite_code) if invite_code.present?
|
||||
invite if invite&.valid_for_use?
|
||||
end
|
||||
end
|
||||
|
||||
def determine_layout
|
||||
|
@ -128,6 +131,10 @@ class Auth::RegistrationsController < Devise::RegistrationsController
|
|||
@sessions = current_user.session_activations
|
||||
end
|
||||
|
||||
def set_strikes
|
||||
@strikes = current_account.strikes.active.latest
|
||||
end
|
||||
|
||||
def require_not_suspended!
|
||||
forbidden if current_account.suspended?
|
||||
end
|
||||
|
|
|
@ -0,0 +1,26 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Disputes::AppealsController < Disputes::BaseController
|
||||
before_action :set_strike
|
||||
|
||||
def create
|
||||
authorize @strike, :appeal?
|
||||
|
||||
@appeal = AppealService.new.call(@strike, appeal_params[:text])
|
||||
|
||||
redirect_to disputes_strike_path(@strike), notice: I18n.t('disputes.strikes.appealed_msg')
|
||||
rescue ActiveRecord::RecordInvalid => e
|
||||
@appeal = e.record
|
||||
render template: 'disputes/strikes/show'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_strike
|
||||
@strike = current_account.strikes.find(params[:strike_id])
|
||||
end
|
||||
|
||||
def appeal_params
|
||||
params.require(:appeal).permit(:text)
|
||||
end
|
||||
end
|
|
@ -0,0 +1,18 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Disputes::BaseController < ApplicationController
|
||||
include Authorization
|
||||
|
||||
layout 'admin'
|
||||
|
||||
skip_before_action :require_functional!
|
||||
|
||||
before_action :set_body_classes
|
||||
before_action :authenticate_user!
|
||||
|
||||
private
|
||||
|
||||
def set_body_classes
|
||||
@body_classes = 'admin'
|
||||
end
|
||||
end
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Disputes::StrikesController < Disputes::BaseController
|
||||
before_action :set_strike
|
||||
|
||||
def show
|
||||
authorize @strike, :show?
|
||||
|
||||
@appeal = @strike.appeal || @strike.build_appeal
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_strike
|
||||
@strike = AccountWarning.find(params[:id])
|
||||
end
|
||||
end
|
|
@ -1,10 +1,10 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::AccountModerationNotesHelper
|
||||
def admin_account_link_to(account)
|
||||
def admin_account_link_to(account, path: nil)
|
||||
return if account.nil?
|
||||
|
||||
link_to admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||
link_to path || admin_account_path(account.id), class: name_tag_classes(account), title: account.acct do
|
||||
safe_join([
|
||||
image_tag(account.avatar.url, width: 15, height: 15, alt: display_name(account), class: 'avatar'),
|
||||
content_tag(:span, account.acct, class: 'username'),
|
||||
|
|
|
@ -33,6 +33,8 @@ module Admin::ActionLogsHelper
|
|||
"#{record.ip}/#{record.ip.prefix} (#{I18n.t("simple_form.labels.ip_block.severities.#{record.severity}")})"
|
||||
when 'Instance'
|
||||
record.domain
|
||||
when 'Appeal'
|
||||
link_to record.account.acct, disputes_strike_path(record.strike)
|
||||
end
|
||||
end
|
||||
|
||||
|
|
|
@ -0,0 +1,20 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
module Admin::Trends::StatusesHelper
|
||||
def one_line_preview(status)
|
||||
text = begin
|
||||
if status.local?
|
||||
status.text.split("\n").first
|
||||
else
|
||||
Nokogiri::HTML(status.text).css('html > body > *').first&.text
|
||||
end
|
||||
end
|
||||
|
||||
return '' if text.blank?
|
||||
|
||||
html = Formatter.instance.send(:encode, text)
|
||||
html = Formatter.instance.send(:encode_custom_emojis, html, status.emojis, prefers_autoplay?)
|
||||
|
||||
html.html_safe # rubocop:disable Rails/OutputSafety
|
||||
end
|
||||
end
|
|
@ -170,7 +170,7 @@ class EmojiPickerMenu extends React.PureComponent {
|
|||
|
||||
state = {
|
||||
modifierOpen: false,
|
||||
placement: null,
|
||||
readyToFocus: false,
|
||||
};
|
||||
|
||||
handleDocumentClick = e => {
|
||||
|
@ -182,6 +182,16 @@ class EmojiPickerMenu extends React.PureComponent {
|
|||
componentDidMount () {
|
||||
document.addEventListener('click', this.handleDocumentClick, false);
|
||||
document.addEventListener('touchend', this.handleDocumentClick, listenerOptions);
|
||||
|
||||
// Because of https://github.com/react-bootstrap/react-bootstrap/issues/2614 we need
|
||||
// to wait for a frame before focusing
|
||||
requestAnimationFrame(() => {
|
||||
this.setState({ readyToFocus: true });
|
||||
if (this.node) {
|
||||
const element = this.node.querySelector('input[type="search"]');
|
||||
if (element) element.focus();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
componentWillUnmount () {
|
||||
|
@ -281,7 +291,7 @@ class EmojiPickerMenu extends React.PureComponent {
|
|||
showSkinTones={false}
|
||||
backgroundImageFn={backgroundImageFn}
|
||||
notFound={notFoundFn}
|
||||
autoFocus
|
||||
autoFocus={this.state.readyToFocus}
|
||||
emojiTooltip
|
||||
/>
|
||||
|
||||
|
@ -314,6 +324,7 @@ class EmojiPickerDropdown extends React.PureComponent {
|
|||
state = {
|
||||
active: false,
|
||||
loading: false,
|
||||
placement: null,
|
||||
};
|
||||
|
||||
setRef = (c) => {
|
||||
|
|
|
@ -151,13 +151,7 @@ function main() {
|
|||
});
|
||||
|
||||
delegate(document, '.sidebar__toggle__icon', 'click', () => {
|
||||
const target = document.querySelector('.sidebar ul');
|
||||
|
||||
if (target.style.display === 'block') {
|
||||
target.style.display = 'none';
|
||||
} else {
|
||||
target.style.display = 'block';
|
||||
}
|
||||
document.querySelector('.sidebar ul').classList.toggle('visible');
|
||||
});
|
||||
|
||||
// Empty the honeypot fields in JS in case something like an extension
|
||||
|
|
|
@ -322,6 +322,10 @@ $content-width: 840px;
|
|||
|
||||
& > ul {
|
||||
display: none;
|
||||
|
||||
&.visible {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
ul a,
|
||||
|
@ -594,12 +598,16 @@ body,
|
|||
}
|
||||
|
||||
.log-entry {
|
||||
display: block;
|
||||
line-height: 20px;
|
||||
padding: 15px;
|
||||
padding-left: 15px * 2 + 40px;
|
||||
background: $ui-base-color;
|
||||
border-bottom: 1px solid darken($ui-base-color, 8%);
|
||||
position: relative;
|
||||
text-decoration: none;
|
||||
color: $darker-text-color;
|
||||
font-size: 14px;
|
||||
|
||||
&:first-child {
|
||||
border-top-left-radius: 4px;
|
||||
|
@ -612,15 +620,12 @@ body,
|
|||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&:hover {
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
background: lighten($ui-base-color, 4%);
|
||||
}
|
||||
|
||||
&__header {
|
||||
color: $darker-text-color;
|
||||
font-size: 14px;
|
||||
}
|
||||
|
||||
&__avatar {
|
||||
position: absolute;
|
||||
left: 15px;
|
||||
|
@ -656,6 +661,18 @@ body,
|
|||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
|
||||
&--inactive {
|
||||
.log-entry__title {
|
||||
text-decoration: line-through;
|
||||
}
|
||||
|
||||
a,
|
||||
.username,
|
||||
.target {
|
||||
color: $darker-text-color;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
a.name-tag,
|
||||
|
@ -1191,6 +1208,17 @@ a.sparkline {
|
|||
font-weight: 600;
|
||||
padding: 4px 0;
|
||||
}
|
||||
|
||||
a {
|
||||
color: $ui-highlight-color;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
&--horizontal {
|
||||
|
@ -1467,3 +1495,56 @@ a.sparkline {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
.strike-card {
|
||||
padding: 15px;
|
||||
border-radius: 4px;
|
||||
background: $ui-base-color;
|
||||
font-size: 15px;
|
||||
line-height: 20px;
|
||||
word-wrap: break-word;
|
||||
font-weight: 400;
|
||||
color: $primary-text-color;
|
||||
|
||||
p {
|
||||
margin-bottom: 20px;
|
||||
unicode-bidi: plaintext;
|
||||
|
||||
&:last-child {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
}
|
||||
|
||||
&__statuses-list {
|
||||
border-radius: 4px;
|
||||
border: 1px solid darken($ui-base-color, 8%);
|
||||
font-size: 13px;
|
||||
line-height: 18px;
|
||||
overflow: hidden;
|
||||
|
||||
&__item {
|
||||
padding: 16px;
|
||||
background: lighten($ui-base-color, 2%);
|
||||
border-bottom: 1px solid darken($ui-base-color, 8%);
|
||||
|
||||
&:last-child {
|
||||
border-bottom: 0;
|
||||
}
|
||||
|
||||
&__meta {
|
||||
color: $darker-text-color;
|
||||
}
|
||||
|
||||
a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
|
||||
&:hover,
|
||||
&:focus,
|
||||
&:active {
|
||||
text-decoration: underline;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,6 +90,20 @@
|
|||
.column-4 {
|
||||
display: none;
|
||||
}
|
||||
|
||||
.column-2 h4 {
|
||||
display: none;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
.legal-xs {
|
||||
display: none;
|
||||
text-align: center;
|
||||
padding-top: 20px;
|
||||
|
||||
@media screen and (max-width: $no-gap-breakpoint) {
|
||||
display: block;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -105,7 +119,8 @@
|
|||
}
|
||||
}
|
||||
|
||||
ul a {
|
||||
ul a,
|
||||
.legal-xs a {
|
||||
text-decoration: none;
|
||||
color: lighten($ui-base-color, 34%);
|
||||
|
||||
|
|
|
@ -8,6 +8,7 @@ class ActivityPub::Activity::Announce < ActivityPub::Activity
|
|||
original_status = status_from_object
|
||||
|
||||
return reject_payload! if original_status.nil? || !announceable?(original_status)
|
||||
return if requested_through_relay?
|
||||
|
||||
@status = Status.find_by(account: @account, reblog: original_status)
|
||||
|
||||
|
|
|
@ -499,7 +499,7 @@ class FeedManager
|
|||
|
||||
return false if active_filters.empty?
|
||||
|
||||
combined_regex = active_filters.reduce { |memo, obj| Regexp.union(memo, obj) }
|
||||
combined_regex = Regexp.union(active_filters)
|
||||
status = status.reblog if status.reblog?
|
||||
|
||||
combined_text = [
|
||||
|
|
|
@ -2,19 +2,21 @@
|
|||
|
||||
class SearchQueryTransformer < Parslet::Transform
|
||||
class Query
|
||||
attr_reader :should_clauses, :must_not_clauses, :must_clauses
|
||||
attr_reader :should_clauses, :must_not_clauses, :must_clauses, :filter_clauses
|
||||
|
||||
def initialize(clauses)
|
||||
grouped = clauses.chunk(&:operator).to_h
|
||||
@should_clauses = grouped.fetch(:should, [])
|
||||
@must_not_clauses = grouped.fetch(:must_not, [])
|
||||
@must_clauses = grouped.fetch(:must, [])
|
||||
@filter_clauses = grouped.fetch(:filter, [])
|
||||
end
|
||||
|
||||
def apply(search)
|
||||
should_clauses.each { |clause| search = search.query.should(clause_to_query(clause)) }
|
||||
must_clauses.each { |clause| search = search.query.must(clause_to_query(clause)) }
|
||||
must_not_clauses.each { |clause| search = search.query.must_not(clause_to_query(clause)) }
|
||||
filter_clauses.each { |clause| search = search.filter(**clause_to_filter(clause)) }
|
||||
search.query.minimum_should_match(1)
|
||||
end
|
||||
|
||||
|
@ -30,6 +32,15 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
raise "Unexpected clause type: #{clause}"
|
||||
end
|
||||
end
|
||||
|
||||
def clause_to_filter(clause)
|
||||
case clause
|
||||
when PrefixClause
|
||||
{ term: { clause.filter => clause.term } }
|
||||
else
|
||||
raise "Unexpected clause type: #{clause}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
class Operator
|
||||
|
@ -69,11 +80,33 @@ class SearchQueryTransformer < Parslet::Transform
|
|||
end
|
||||
end
|
||||
|
||||
class PrefixClause
|
||||
attr_reader :filter, :operator, :term
|
||||
|
||||
def initialize(prefix, term)
|
||||
@operator = :filter
|
||||
case prefix
|
||||
when 'from'
|
||||
@filter = :account_id
|
||||
username, domain = term.split('@')
|
||||
account = Account.find_remote(username, domain)
|
||||
|
||||
raise "Account not found: #{term}" unless account
|
||||
|
||||
@term = account.id
|
||||
else
|
||||
raise "Unknown prefix: #{prefix}"
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
rule(clause: subtree(:clause)) do
|
||||
prefix = clause[:prefix][:term].to_s if clause[:prefix]
|
||||
operator = clause[:operator]&.to_s
|
||||
|
||||
if clause[:term]
|
||||
if clause[:prefix]
|
||||
PrefixClause.new(prefix, clause[:term].to_s)
|
||||
elsif clause[:term]
|
||||
TermClause.new(prefix, operator, clause[:term].to_s)
|
||||
elsif clause[:shortcode]
|
||||
TermClause.new(prefix, operator, ":#{clause[:term]}:")
|
||||
|
|
|
@ -15,6 +15,16 @@ class AdminMailer < ApplicationMailer
|
|||
end
|
||||
end
|
||||
|
||||
def new_appeal(recipient, appeal)
|
||||
@appeal = appeal
|
||||
@me = recipient
|
||||
@instance = Rails.configuration.x.local_domain
|
||||
|
||||
locale_for_account(@me) do
|
||||
mail to: @me.user_email, subject: I18n.t('admin_mailer.new_appeal.subject', instance: @instance, username: @appeal.account.username)
|
||||
end
|
||||
end
|
||||
|
||||
def new_pending_account(recipient, user)
|
||||
@account = user.account
|
||||
@me = recipient
|
||||
|
|
|
@ -173,6 +173,26 @@ class UserMailer < Devise::Mailer
|
|||
end
|
||||
end
|
||||
|
||||
def appeal_approved(user, appeal)
|
||||
@resource = user
|
||||
@instance = Rails.configuration.x.local_domain
|
||||
@appeal = appeal
|
||||
|
||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||
mail to: @resource.email, subject: I18n.t('user_mailer.appeal_approved.subject', date: l(@appeal.created_at))
|
||||
end
|
||||
end
|
||||
|
||||
def appeal_rejected(user, appeal)
|
||||
@resource = user
|
||||
@instance = Rails.configuration.x.local_domain
|
||||
@appeal = appeal
|
||||
|
||||
I18n.with_locale(@resource.locale || I18n.default_locale) do
|
||||
mail to: @resource.email, subject: I18n.t('user_mailer.appeal_rejected.subject', date: l(@appeal.created_at))
|
||||
end
|
||||
end
|
||||
|
||||
def sign_in_token(user, remote_ip, user_agent, timestamp)
|
||||
@resource = user
|
||||
@instance = Rails.configuration.x.local_domain
|
||||
|
|
|
@ -274,6 +274,10 @@ class Account < ApplicationRecord
|
|||
true
|
||||
end
|
||||
|
||||
def previous_strikes_count
|
||||
strikes.where(overruled_at: nil).count
|
||||
end
|
||||
|
||||
def keypair
|
||||
@keypair ||= OpenSSL::PKey::RSA.new(private_key || public_key)
|
||||
end
|
||||
|
|
|
@ -24,6 +24,8 @@ class AccountFilter
|
|||
scope = Account.includes(:account_stat, user: [:ips, :invite_request]).without_instance_actor.reorder(nil)
|
||||
|
||||
params.each do |key, value|
|
||||
next if key.to_s == 'page'
|
||||
|
||||
scope.merge!(scope_for(key, value.to_s.strip)) if value.present?
|
||||
end
|
||||
|
||||
|
@ -49,7 +51,7 @@ class AccountFilter
|
|||
when 'email'
|
||||
accounts_with_users.merge(User.matches_email(value))
|
||||
when 'ip'
|
||||
valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value)) : Account.none
|
||||
valid_ip?(value) ? accounts_with_users.merge(User.matches_ip(value).group('users.id, accounts.id')) : Account.none
|
||||
when 'invited_by'
|
||||
invited_by_scope(value)
|
||||
when 'order'
|
||||
|
|
|
@ -12,6 +12,7 @@
|
|||
# updated_at :datetime not null
|
||||
# report_id :bigint(8)
|
||||
# status_ids :string is an Array
|
||||
# overruled_at :datetime
|
||||
#
|
||||
|
||||
class AccountWarning < ApplicationRecord
|
||||
|
@ -28,12 +29,17 @@ class AccountWarning < ApplicationRecord
|
|||
belongs_to :target_account, class_name: 'Account', inverse_of: :strikes
|
||||
belongs_to :report, optional: true
|
||||
|
||||
has_one :appeal, dependent: :destroy
|
||||
has_one :appeal, dependent: :destroy, inverse_of: :strike
|
||||
|
||||
scope :latest, -> { order(id: :desc) }
|
||||
scope :custom, -> { where.not(text: '') }
|
||||
scope :active, -> { where(overruled_at: nil).or(where('account_warnings.overruled_at >= ?', 30.days.ago)) }
|
||||
|
||||
def statuses
|
||||
Status.with_discarded.where(id: status_ids || [])
|
||||
end
|
||||
|
||||
def overruled?
|
||||
overruled_at.present?
|
||||
end
|
||||
end
|
||||
|
|
|
@ -8,6 +8,8 @@ class Admin::ActionLogFilter
|
|||
).freeze
|
||||
|
||||
ACTION_TYPE_MAP = {
|
||||
approve_appeal: { target_type: 'Appeal', action: 'approve' }.freeze,
|
||||
reject_appeal: { target_type: 'Appeal', action: 'reject' }.freeze,
|
||||
assigned_to_self_report: { target_type: 'Report', action: 'assigned_to_self' }.freeze,
|
||||
change_email_user: { target_type: 'User', action: 'change_email' }.freeze,
|
||||
confirm_user: { target_type: 'User', action: 'confirm' }.freeze,
|
||||
|
|
|
@ -0,0 +1,49 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class Admin::AppealFilter
|
||||
KEYS = %i(
|
||||
status
|
||||
).freeze
|
||||
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def results
|
||||
scope = Appeal.order(id: :desc)
|
||||
|
||||
params.each do |key, value|
|
||||
next if %w(page).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 'status'
|
||||
status_scope(value)
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
end
|
||||
|
||||
def status_scope(value)
|
||||
case value
|
||||
when 'approved'
|
||||
Appeal.approved
|
||||
when 'rejected'
|
||||
Appeal.rejected
|
||||
when 'pending'
|
||||
Appeal.pending
|
||||
else
|
||||
raise "Unknown status: #{value}"
|
||||
end
|
||||
end
|
||||
end
|
|
@ -31,7 +31,7 @@ class Admin::StatusFilter
|
|||
def scope_for(key, value)
|
||||
case key.to_s
|
||||
when 'media'
|
||||
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id)
|
||||
Status.joins(:media_attachments).merge(@account.media_attachments.reorder(nil)).group(:id).reorder('statuses.id desc')
|
||||
when 'id'
|
||||
Status.where(id: value)
|
||||
else
|
||||
|
|
|
@ -0,0 +1,60 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: appeals
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8) not null
|
||||
# account_warning_id :bigint(8) not null
|
||||
# text :text default(""), not null
|
||||
# approved_at :datetime
|
||||
# approved_by_account_id :bigint(8)
|
||||
# rejected_at :datetime
|
||||
# rejected_by_account_id :bigint(8)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
class Appeal < ApplicationRecord
|
||||
MAX_STRIKE_AGE = 20.days
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :strike, class_name: 'AccountWarning', foreign_key: 'account_warning_id'
|
||||
belongs_to :approved_by_account, class_name: 'Account', optional: true
|
||||
belongs_to :rejected_by_account, class_name: 'Account', optional: true
|
||||
|
||||
validates :text, presence: true, length: { maximum: 2_000 }
|
||||
validates :account_warning_id, uniqueness: true
|
||||
|
||||
validate :validate_time_frame, on: :create
|
||||
|
||||
scope :approved, -> { where.not(approved_at: nil) }
|
||||
scope :rejected, -> { where.not(rejected_at: nil) }
|
||||
scope :pending, -> { where(approved_at: nil, rejected_at: nil) }
|
||||
|
||||
def pending?
|
||||
!approved? && !rejected?
|
||||
end
|
||||
|
||||
def approved?
|
||||
approved_at.present?
|
||||
end
|
||||
|
||||
def rejected?
|
||||
rejected_at.present?
|
||||
end
|
||||
|
||||
def approve!(current_account)
|
||||
update!(approved_at: Time.now.utc, approved_by_account: current_account)
|
||||
end
|
||||
|
||||
def reject!(current_account)
|
||||
update!(rejected_at: Time.now.utc, rejected_by_account: current_account)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_time_frame
|
||||
errors.add(:base, I18n.t('strikes.errors.too_late')) if strike.created_at < MAX_STRIKE_AGE.ago
|
||||
end
|
||||
end
|
|
@ -111,7 +111,7 @@ class User < ApplicationRecord
|
|||
scope :inactive, -> { where(arel_table[:current_sign_in_at].lt(ACTIVE_DURATION.ago)) }
|
||||
scope :active, -> { confirmed.where(arel_table[:current_sign_in_at].gteq(ACTIVE_DURATION.ago)).joins(:account).where(accounts: { suspended_at: nil }) }
|
||||
scope :matches_email, ->(value) { where(arel_table[:email].matches("#{value}%")) }
|
||||
scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value) }
|
||||
scope :matches_ip, ->(value) { left_joins(:ips).where('user_ips.ip <<= ?', value).group('users.id') }
|
||||
scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
|
||||
|
||||
before_validation :sanitize_languages
|
||||
|
@ -265,6 +265,10 @@ class User < ApplicationRecord
|
|||
settings.notification_emails['pending_account']
|
||||
end
|
||||
|
||||
def allows_appeal_emails?
|
||||
settings.notification_emails['appeal']
|
||||
end
|
||||
|
||||
def allows_trending_tag_emails?
|
||||
settings.notification_emails['trending_tag']
|
||||
end
|
||||
|
|
|
@ -0,0 +1,17 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AccountWarningPolicy < ApplicationPolicy
|
||||
def show?
|
||||
target? || staff?
|
||||
end
|
||||
|
||||
def appeal?
|
||||
target? && record.created_at >= Appeal::MAX_STRIKE_AGE.ago
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def target?
|
||||
record.target_account_id == current_account&.id
|
||||
end
|
||||
end
|
|
@ -0,0 +1,13 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AppealPolicy < ApplicationPolicy
|
||||
def index?
|
||||
staff?
|
||||
end
|
||||
|
||||
def approve?
|
||||
record.pending? && staff?
|
||||
end
|
||||
|
||||
alias reject? approve?
|
||||
end
|
|
@ -0,0 +1,28 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class AppealService < BaseService
|
||||
def call(strike, text)
|
||||
@strike = strike
|
||||
@text = text
|
||||
|
||||
create_appeal!
|
||||
notify_staff!
|
||||
|
||||
@appeal
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def create_appeal!
|
||||
@appeal = @strike.create_appeal!(
|
||||
text: @text,
|
||||
account: @strike.target_account
|
||||
)
|
||||
end
|
||||
|
||||
def notify_staff!
|
||||
User.staff.includes(:account).each do |u|
|
||||
AdminMailer.new_appeal(u.account, @appeal).deliver_later if u.allows_appeal_emails?
|
||||
end
|
||||
end
|
||||
end
|