Merge pull request #1995 from ClearlyClaire/glitch-soc/merge-upstream

Merge upstream changes
This commit is contained in:
Claire 2022-12-07 10:50:52 +01:00 committed by GitHub
commit 61c6e06c95
25 changed files with 479 additions and 63 deletions

View file

@ -25,7 +25,7 @@ jobs:
- name: Set up Ruby - name: Set up Ruby
uses: ruby/setup-ruby@v1 uses: ruby/setup-ruby@v1
with: with:
ruby-version: '3.0' ruby-version: .ruby-version
bundler-cache: true bundler-cache: true
- name: Check locale file normalization - name: Check locale file normalization
run: bundle exec i18n-tasks check-normalized run: bundle exec i18n-tasks check-normalized

View file

@ -53,7 +53,7 @@ jobs:
- name: Set-up Node.js - name: Set-up Node.js
uses: actions/setup-node@v3 uses: actions/setup-node@v3
with: with:
node-version: 16.x node-version-file: .nvmrc
cache: yarn cache: yarn
- name: Install dependencies - name: Install dependencies
run: yarn install --frozen-lockfile run: yarn install --frozen-lockfile

71
Vagrantfile vendored
View file

@ -3,16 +3,14 @@
ENV["PORT"] ||= "3000" ENV["PORT"] ||= "3000"
$provision = <<SCRIPT $provisionA = <<SCRIPT
cd /vagrant # This is where the host folder/repo is mounted
# Add the yarn repo + yarn repo keys # Add the yarn repo + yarn repo keys
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add - curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | sudo apt-key add -
sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main' sudo apt-add-repository 'deb https://dl.yarnpkg.com/debian/ stable main'
# Add repo for NodeJS # Add repo for NodeJS
curl -sL https://deb.nodesource.com/setup_14.x | sudo bash - curl -sL https://deb.nodesource.com/setup_16.x | sudo bash -
# Add firewall rule to redirect 80 to PORT and save # Add firewall rule to redirect 80 to PORT and save
sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]} sudo iptables -t nat -A PREROUTING -p tcp --dport 80 -j REDIRECT --to-port #{ENV["PORT"]}
@ -33,32 +31,56 @@ sudo apt-get install \
redis-tools \ redis-tools \
postgresql \ postgresql \
postgresql-contrib \ postgresql-contrib \
yarn \
libicu-dev \ libicu-dev \
libidn11-dev \ libidn11-dev \
libreadline-dev \ libreadline6-dev \
libpam0g-dev \ autoconf \
bison \
build-essential \
ffmpeg \
file \
gcc \
libffi-dev \
libgdbm-dev \
libjemalloc-dev \
libncurses5-dev \
libprotobuf-dev \
libssl-dev \
libyaml-dev \
pkg-config \
protobuf-compiler \
zlib1g-dev \
-y -y
# Install rvm # Install rvm
read RUBY_VERSION < .ruby-version sudo apt-add-repository -y ppa:rael-gc/rvm
sudo apt-get install rvm -y
curl -sSL https://rvm.io/mpapis.asc | gpg --import sudo usermod -a -G rvm $USER
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION SCRIPT
source /home/vagrant/.rvm/scripts/rvm
$provisionB = <<SCRIPT
source "/etc/profile.d/rvm.sh"
# Install Ruby # Install Ruby
rvm reinstall ruby-$RUBY_VERSION --disable-binary read RUBY_VERSION < /vagrant/.ruby-version
rvm install ruby-$RUBY_VERSION --disable-binary
# Configure database # Configure database
sudo -u postgres createuser -U postgres vagrant -s sudo -u postgres createuser -U postgres vagrant -s
sudo -u postgres createdb -U postgres mastodon_development sudo -u postgres createdb -U postgres mastodon_development
# Install gems and node modules cd /vagrant # This is where the host folder/repo is mounted
# Install gems
gem install bundler foreman gem install bundler foreman
bundle install bundle install
# Install node modules
sudo corepack enable
yarn set version classic
yarn install yarn install
# Build Mastodon # Build Mastodon
@ -72,18 +94,11 @@ echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
SCRIPT SCRIPT
$start = <<SCRIPT
echo 'To start server'
echo ' $ vagrant ssh -c "cd /vagrant && foreman start"'
SCRIPT
VAGRANTFILE_API_VERSION = "2" VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config| Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/bionic64" config.vm.box = "ubuntu/focal64"
config.vm.provider :virtualbox do |vb| config.vm.provider :virtualbox do |vb|
vb.name = "mastodon" vb.name = "mastodon"
@ -100,7 +115,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Use "virtio" network interfaces for better performance. # Use "virtio" network interfaces for better performance.
vb.customize ["modifyvm", :id, "--nictype1", "virtio"] vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
vb.customize ["modifyvm", :id, "--nictype2", "virtio"] vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
end end
# This uses the vagrant-hostsupdater plugin, and lets you # This uses the vagrant-hostsupdater plugin, and lets you
@ -118,7 +132,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
end end
if config.vm.networks.any? { |type, options| type == :private_network } if config.vm.networks.any? { |type, options| type == :private_network }
config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'vers=3', 'tcp', 'actimeo=1'] config.vm.synced_folder ".", "/vagrant", type: "nfs", mount_options: ['rw', 'actimeo=1']
else else
config.vm.synced_folder ".", "/vagrant" config.vm.synced_folder ".", "/vagrant"
end end
@ -129,9 +143,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.network :forwarded_port, guest: 8080, host: 8080 config.vm.network :forwarded_port, guest: 8080, host: 8080
# Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision' # Full provisioning script, only runs on first 'vagrant up' or with 'vagrant provision'
config.vm.provision :shell, inline: $provision, privileged: false config.vm.provision :shell, inline: $provisionA, privileged: false, reset: true
config.vm.provision :shell, inline: $provisionB, privileged: false
# Start up script, runs on every 'vagrant up' config.vm.post_up_message = <<MESSAGE
config.vm.provision :shell, inline: $start, run: 'always', privileged: false To start server
$ vagrant ssh -c "cd /vagrant && foreman start"
MESSAGE
end end

View file

@ -55,12 +55,14 @@ module Admin
def approve def approve
authorize @account.user, :approve? authorize @account.user, :approve?
@account.user.approve! @account.user.approve!
log_action :approve, @account.user
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct) redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.approved_msg', username: @account.acct)
end end
def reject def reject
authorize @account.user, :reject? authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false) DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
log_action :reject, @account.user
redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct) redirect_to admin_accounts_path(status: 'pending'), notice: I18n.t('admin.accounts.rejected_msg', username: @account.acct)
end end

View file

@ -54,12 +54,14 @@ class Api::V1::Admin::AccountsController < Api::BaseController
def approve def approve
authorize @account.user, :approve? authorize @account.user, :approve?
@account.user.approve! @account.user.approve!
log_action :approve, @account.user
render json: @account, serializer: REST::Admin::AccountSerializer render json: @account, serializer: REST::Admin::AccountSerializer
end end
def reject def reject
authorize @account.user, :reject? authorize @account.user, :reject?
DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false) DeleteAccountService.new.call(@account, reserve_email: false, reserve_username: false)
log_action :reject, @account.user
render_empty render_empty
end end

View file

@ -13,7 +13,7 @@ class Api::V1::FiltersController < Api::BaseController
def create def create
ApplicationRecord.transaction do ApplicationRecord.transaction do
filter_category = current_account.custom_filters.create!(resource_params) filter_category = current_account.custom_filters.create!(filter_params)
@filter = filter_category.keywords.create!(keyword_params) @filter = filter_category.keywords.create!(keyword_params)
end end
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
end end
def resource_params def resource_params
params.permit(:phrase, :expires_in, :irreversible, context: []) params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
end end
def filter_params def filter_params
resource_params.slice(:expires_in, :irreversible, :context) resource_params.slice(:phrase, :expires_in, :irreversible, :context)
end end
def keyword_params def keyword_params

View file

@ -7,6 +7,7 @@ import Avatar from 'flavours/glitch/components/avatar';
import Permalink from 'flavours/glitch/components/permalink'; import Permalink from 'flavours/glitch/components/permalink';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal';
const Account = connect(state => ({ const Account = connect(state => ({
account: state.getIn(['accounts', me]), account: state.getIn(['accounts', me]),
@ -16,7 +17,14 @@ const Account = connect(state => ({
</Permalink> </Permalink>
)); ));
export default @withRouter const mapDispatchToProps = (dispatch) => ({
openClosedRegistrationsModal() {
dispatch(openModal('CLOSED_REGISTRATIONS'));
},
});
export default @connect(null, mapDispatchToProps)
@withRouter
class Header extends React.PureComponent { class Header extends React.PureComponent {
static contextTypes = { static contextTypes = {
@ -24,12 +32,13 @@ class Header extends React.PureComponent {
}; };
static propTypes = { static propTypes = {
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object, location: PropTypes.object,
}; };
render () { render () {
const { signedIn } = this.context.identity; const { signedIn } = this.context.identity;
const { location } = this.props; const { location, openClosedRegistrationsModal } = this.props;
let content; let content;
@ -41,10 +50,26 @@ class Header extends React.PureComponent {
</> </>
); );
} else { } else {
let signupButton;
if (registrationsOpen) {
signupButton = (
<a href='/auth/sign_up' className='button button-tertiary'>
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
</a>
);
} else {
signupButton = (
<button className='button button-tertiary' onClick={openClosedRegistrationsModal}>
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
</button>
);
}
content = ( content = (
<> <>
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> <a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
<a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a> {signupButton}
</> </>
); );
} }

View file

@ -65,6 +65,7 @@ $ui-header-height: 55px;
z-index: 2; z-index: 2;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
overflow: hidden;
&__logo { &__logo {
display: inline-flex; display: inline-flex;
@ -81,10 +82,15 @@ $ui-header-height: 55px;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 0 10px; padding: 0 10px;
overflow: hidden;
.button { .button {
flex: 0 0 auto; flex: 0 0 auto;
} }
.button-tertiary {
flex-shrink: 1;
}
} }
} }

View file

@ -1306,7 +1306,8 @@ img.modal-warning {
width: 600px; width: 600px;
background: $ui-base-color; background: $ui-base-color;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow-x: hidden;
overflow-y: auto;
position: relative; position: relative;
display: block; display: block;
padding: 20px; padding: 20px;

View file

@ -6,6 +6,7 @@ import { registrationsOpen, me } from 'mastodon/initial_state';
import Avatar from 'mastodon/components/avatar'; import Avatar from 'mastodon/components/avatar';
import PropTypes from 'prop-types'; import PropTypes from 'prop-types';
import { connect } from 'react-redux'; import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
const Account = connect(state => ({ const Account = connect(state => ({
account: state.getIn(['accounts', me]), account: state.getIn(['accounts', me]),
@ -15,7 +16,14 @@ const Account = connect(state => ({
</Link> </Link>
)); ));
export default @withRouter const mapDispatchToProps = (dispatch) => ({
openClosedRegistrationsModal() {
dispatch(openModal('CLOSED_REGISTRATIONS'));
},
});
export default @connect(null, mapDispatchToProps)
@withRouter
class Header extends React.PureComponent { class Header extends React.PureComponent {
static contextTypes = { static contextTypes = {
@ -23,12 +31,13 @@ class Header extends React.PureComponent {
}; };
static propTypes = { static propTypes = {
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object, location: PropTypes.object,
}; };
render () { render () {
const { signedIn } = this.context.identity; const { signedIn } = this.context.identity;
const { location } = this.props; const { location, openClosedRegistrationsModal } = this.props;
let content; let content;
@ -40,10 +49,26 @@ class Header extends React.PureComponent {
</> </>
); );
} else { } else {
let signupButton;
if (registrationsOpen) {
signupButton = (
<a href='/auth/sign_up' className='button button-tertiary'>
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
</a>
);
} else {
signupButton = (
<button className='button button-tertiary' onClick={openClosedRegistrationsModal}>
<FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' />
</button>
);
}
content = ( content = (
<> <>
<a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a> <a href='/auth/sign_in' className='button'><FormattedMessage id='sign_in_banner.sign_in' defaultMessage='Sign in' /></a>
<a href={registrationsOpen ? '/auth/sign_up' : 'https://joinmastodon.org/servers'} className='button button-tertiary'><FormattedMessage id='sign_in_banner.create_account' defaultMessage='Create account' /></a> {signupButton}
</> </>
); );
} }

View file

@ -2217,6 +2217,7 @@ $ui-header-height: 55px;
z-index: 2; z-index: 2;
justify-content: space-between; justify-content: space-between;
align-items: center; align-items: center;
overflow: hidden;
&__logo { &__logo {
display: inline-flex; display: inline-flex;
@ -2233,10 +2234,15 @@ $ui-header-height: 55px;
align-items: center; align-items: center;
gap: 10px; gap: 10px;
padding: 0 10px; padding: 0 10px;
overflow: hidden;
.button { .button {
flex: 0 0 auto; flex: 0 0 auto;
} }
.button-tertiary {
flex-shrink: 1;
}
} }
} }
@ -7138,10 +7144,12 @@ noscript {
.verified { .verified {
border: 1px solid rgba($valid-value-color, 0.5); border: 1px solid rgba($valid-value-color, 0.5);
margin-top: -1px;
&:first-child { &:first-child {
border-top-left-radius: 4px; border-top-left-radius: 4px;
border-top-right-radius: 4px; border-top-right-radius: 4px;
margin-top: 0;
} }
&:last-child { &:last-child {
@ -7973,7 +7981,8 @@ noscript {
width: 600px; width: 600px;
background: $ui-base-color; background: $ui-base-color;
border-radius: 8px; border-radius: 8px;
overflow: hidden; overflow-x: hidden;
overflow-y: auto;
position: relative; position: relative;
display: block; display: block;
padding: 20px; padding: 20px;

View file

@ -59,7 +59,7 @@ class AccountMigration < ApplicationRecord
def set_target_account def set_target_account
self.target_account = ResolveAccountService.new.call(acct, skip_cache: true) self.target_account = ResolveAccountService.new.call(acct, skip_cache: true)
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
# Validation will take care of it # Validation will take care of it
end end

View file

@ -14,7 +14,6 @@ module DomainMaterializable
Instance.refresh Instance.refresh
count_unique_subdomains! count_unique_subdomains!
end end
def count_unique_subdomains! def count_unique_subdomains!

View file

@ -54,7 +54,7 @@ class CustomFilter < ApplicationRecord
end end
def irreversible=(value) def irreversible=(value)
self.action = value ? :hide : :warn self.action = ActiveModel::Type::Boolean.new.cast(value) ? :hide : :warn
end end
def irreversible? def irreversible?

View file

@ -32,7 +32,7 @@ class Form::Redirect
def set_target_account def set_target_account
@target_account = ResolveAccountService.new.call(acct, skip_cache: true) @target_account = ResolveAccountService.new.call(acct, skip_cache: true)
rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error rescue Webfinger::Error, HTTP::Error, OpenSSL::SSL::SSLError, Mastodon::Error, Addressable::URI::InvalidURIError
# Validation will take care of it # Validation will take care of it
end end

View file

@ -71,6 +71,7 @@ module Mastodon
:af, :af,
:ar, :ar,
:ast, :ast,
:be,
:bg, :bg,
:bn, :bn,
:br, :br,

View file

@ -2,6 +2,7 @@ default: &default
adapter: postgresql adapter: postgresql
pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %> pool: <%= ENV["DB_POOL"] || ENV['MAX_THREADS'] || 5 %>
timeout: 5000 timeout: 5000
connect_timeout: 15
encoding: unicode encoding: unicode
sslmode: <%= ENV['DB_SSLMODE'] || "prefer" %> sslmode: <%= ENV['DB_SSLMODE'] || "prefer" %>

View file

@ -79,69 +79,72 @@ class BackfillAdminActionLogs < ActiveRecord::Migration[6.1]
safety_assured do safety_assured do
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log| AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
next if log.account.nil? next if log.account.nil?
log.update(human_identifier: log.account.acct) log.update_attribute('human_identifier', log.account.acct)
end end
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log| AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
next if log.user.nil? next if log.user.nil?
log.update(human_identifier: log.user.account.acct, route_param: log.user.account_id) log.update_attribute('human_identifier', log.user.account.acct)
log.update_attribute('route_param', log.user.account_id)
end end
Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text') Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log| AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
next if log.domain_block.nil? next if log.domain_block.nil?
log.update(human_identifier: log.domain_block.domain) log.update_attribute('human_identifier', log.domain_block.domain)
end end
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log| AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
next if log.domain_allow.nil? next if log.domain_allow.nil?
log.update(human_identifier: log.domain_allow.domain) log.update_attribute('human_identifier', log.domain_allow.domain)
end end
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log| AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
next if log.email_domain_block.nil? next if log.email_domain_block.nil?
log.update(human_identifier: log.email_domain_block.domain) log.update_attribute('human_identifier', log.email_domain_block.domain)
end end
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log| AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
next if log.unavailable_domain.nil? next if log.unavailable_domain.nil?
log.update(human_identifier: log.unavailable_domain.domain) log.update_attribute('human_identifier', log.unavailable_domain.domain)
end end
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log| AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
next if log.status.nil? next if log.status.nil?
log.update(human_identifier: log.status.account.acct, permalink: log.status.uri) log.update_attribute('human_identifier', log.status.account.acct)
log.update_attribute('permalink', log.status.uri)
end end
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log| AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
next if log.account_warning.nil? next if log.account_warning.nil?
log.update(human_identifier: log.account_warning.account.acct) log.update_attribute('human_identifier', log.account_warning.account.acct)
end end
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log| AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
next if log.announcement.nil? next if log.announcement.nil?
log.update(human_identifier: log.announcement.text) log.update_attribute('human_identifier', log.announcement.text)
end end
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log| AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
next if log.ip_block.nil? next if log.ip_block.nil?
log.update(human_identifier: "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}") log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
end end
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log| AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
next if log.custom_emoji.nil? next if log.custom_emoji.nil?
log.update(human_identifier: log.custom_emoji.shortcode) log.update_attribute('human_identifier', log.custom_emoji.shortcode)
end end
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log| AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
next if log.canonical_email_block.nil? next if log.canonical_email_block.nil?
log.update(human_identifier: log.canonical_email_block.canonical_email_hash) log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash)
end end
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log| AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
next if log.appeal.nil? next if log.appeal.nil?
log.update(human_identifier: log.appeal.account.acct, route_param: log.appeal.account_warning_id) log.update_attribute('human_identifier', log.appeal.account.acct)
log.update_attribute('route_param', log.appeal.account_warning_id)
end end
end end
end end

View file

@ -0,0 +1,153 @@
# frozen_string_literal: true
class BackfillAdminActionLogsAgain < ActiveRecord::Migration[6.1]
disable_ddl_transaction!
class Account < ApplicationRecord
# Dummy class, to make migration possible across version changes
has_one :user, inverse_of: :account
def local?
domain.nil?
end
def acct
local? ? username : "#{username}@#{domain}"
end
end
class User < ApplicationRecord
# Dummy class, to make migration possible across version changes
belongs_to :account
end
class Status < ApplicationRecord
include RoutingHelper
# Dummy class, to make migration possible across version changes
belongs_to :account
def local?
attributes['local'] || attributes['uri'].nil?
end
def uri
local? ? activity_account_status_url(account, self) : attributes['uri']
end
end
class DomainBlock < ApplicationRecord; end
class DomainAllow < ApplicationRecord; end
class EmailDomainBlock < ApplicationRecord; end
class UnavailableDomain < ApplicationRecord; end
class AccountWarning < ApplicationRecord
# Dummy class, to make migration possible across version changes
belongs_to :account
end
class Announcement < ApplicationRecord; end
class IpBlock < ApplicationRecord; end
class CustomEmoji < ApplicationRecord; end
class CanonicalEmailBlock < ApplicationRecord; end
class Appeal < ApplicationRecord
# Dummy class, to make migration possible across version changes
belongs_to :account
end
class AdminActionLog < ApplicationRecord
# Dummy class, to make migration possible across version changes
# Cannot use usual polymorphic support because of namespacing issues
belongs_to :status, foreign_key: :target_id
belongs_to :account, foreign_key: :target_id
belongs_to :user, foreign_key: :user_id
belongs_to :domain_block, foreign_key: :target_id
belongs_to :domain_allow, foreign_key: :target_id
belongs_to :email_domain_block, foreign_key: :target_id
belongs_to :unavailable_domain, foreign_key: :target_id
belongs_to :account_warning, foreign_key: :target_id
belongs_to :announcement, foreign_key: :target_id
belongs_to :ip_block, foreign_key: :target_id
belongs_to :custom_emoji, foreign_key: :target_id
belongs_to :canonical_email_block, foreign_key: :target_id
belongs_to :appeal, foreign_key: :target_id
end
def up
safety_assured do
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
next if log.account.nil?
log.update_attribute('human_identifier', log.account.acct)
end
AdminActionLog.includes(user: :account).where(target_type: 'User', human_identifier: nil).find_each do |log|
next if log.user.nil?
log.update_attribute('human_identifier', log.user.account.acct)
log.update_attribute('route_param', log.user.account_id)
end
Admin::ActionLog.where(target_type: 'Report', human_identifier: nil).in_batches.update_all('human_identifier = target_id::text')
AdminActionLog.includes(:domain_block).where(target_type: 'DomainBlock').find_each do |log|
next if log.domain_block.nil?
log.update_attribute('human_identifier', log.domain_block.domain)
end
AdminActionLog.includes(:domain_allow).where(target_type: 'DomainAllow').find_each do |log|
next if log.domain_allow.nil?
log.update_attribute('human_identifier', log.domain_allow.domain)
end
AdminActionLog.includes(:email_domain_block).where(target_type: 'EmailDomainBlock').find_each do |log|
next if log.email_domain_block.nil?
log.update_attribute('human_identifier', log.email_domain_block.domain)
end
AdminActionLog.includes(:unavailable_domain).where(target_type: 'UnavailableDomain').find_each do |log|
next if log.unavailable_domain.nil?
log.update_attribute('human_identifier', log.unavailable_domain.domain)
end
AdminActionLog.includes(status: :account).where(target_type: 'Status', human_identifier: nil).find_each do |log|
next if log.status.nil?
log.update_attribute('human_identifier', log.status.account.acct)
log.update_attribute('permalink', log.status.uri)
end
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
next if log.account_warning.nil?
log.update_attribute('human_identifier', log.account_warning.account.acct)
end
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
next if log.announcement.nil?
log.update_attribute('human_identifier', log.announcement.text)
end
AdminActionLog.includes(:ip_block).where(target_type: 'IpBlock', human_identifier: nil).find_each do |log|
next if log.ip_block.nil?
log.update_attribute('human_identifier', "#{log.ip_block.ip}/#{log.ip_block.ip.prefix}")
end
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
next if log.custom_emoji.nil?
log.update_attribute('human_identifier', log.custom_emoji.shortcode)
end
AdminActionLog.includes(:canonical_email_block).where(target_type: 'CanonicalEmailBlock', human_identifier: nil).find_each do |log|
next if log.canonical_email_block.nil?
log.update_attribute('human_identifier', log.canonical_email_block.canonical_email_hash)
end
AdminActionLog.includes(appeal: :account).where(target_type: 'Appeal', human_identifier: nil).find_each do |log|
next if log.appeal.nil?
log.update_attribute('human_identifier', log.appeal.account.acct)
log.update_attribute('route_param', log.appeal.account_warning_id)
end
end
end
def down; end
end

View file

@ -10,7 +10,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_11_04_133904) do ActiveRecord::Schema.define(version: 2022_12_06_114142) do
# These are extensions that must be enabled in order to support this database # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"

View file

@ -43,6 +43,16 @@ namespace :tests do
puts 'CustomFilterKeyword records not created as expected' puts 'CustomFilterKeyword records not created as expected'
exit(1) exit(1)
end end
unless Admin::ActionLog.find_by(target_type: 'DomainBlock', target_id: 1).human_identifier == 'example.org'
puts 'Admin::ActionLog domain block records not updated as expected'
exit(1)
end
unless Admin::ActionLog.find_by(target_type: 'EmailDomainBlock', target_id: 1).human_identifier == 'example.org'
puts 'Admin::ActionLog email domain block records not updated as expected'
exit(1)
end
end end
desc 'Populate the database with test data for 2.4.3' desc 'Populate the database with test data for 2.4.3'
@ -84,8 +94,8 @@ namespace :tests do
VALUES VALUES
(1, 'destroy', 'Account', 1, now(), now()), (1, 'destroy', 'Account', 1, now(), now()),
(1, 'destroy', 'User', 1, now(), now()), (1, 'destroy', 'User', 1, now(), now()),
(1, 'destroy', 'DomainBlock', 1312, now(), now()), (1, 'destroy', 'DomainBlock', 1, now(), now()),
(1, 'destroy', 'EmailDomainBlock', 1312, now(), now()), (1, 'destroy', 'EmailDomainBlock', 1, now(), now()),
(1, 'destroy', 'Status', 1, now(), now()), (1, 'destroy', 'Status', 1, now(), now()),
(1, 'destroy', 'CustomEmoji', 3, now(), now()); (1, 'destroy', 'CustomEmoji', 3, now(), now());
SQL SQL

View file

@ -147,6 +147,87 @@ RSpec.describe Admin::AccountsController, type: :controller do
end end
end end
describe 'POST #approve' do
subject { post :approve, params: { id: account.id } }
let(:current_user) { Fabricate(:user, role: role) }
let(:account) { user.account }
let(:user) { Fabricate(:user) }
before do
account.user.update(approved: false)
end
context 'when user is admin' do
let(:role) { UserRole.find_by(name: 'Admin') }
it 'succeeds in approving account' do
is_expected.to redirect_to admin_accounts_path(status: 'pending')
expect(user.reload).to be_approved
end
it 'logs action' do
is_expected.to have_http_status :found
log_item = Admin::ActionLog.last
expect(log_item).to_not be_nil
expect(log_item.action).to eq :approve
expect(log_item.account_id).to eq current_user.account_id
expect(log_item.target_id).to eq account.user.id
end
end
context 'when user is not admin' do
let(:role) { UserRole.everyone }
it 'fails to approve account' do
is_expected.to have_http_status :forbidden
expect(user.reload).not_to be_approved
end
end
end
describe 'POST #reject' do
subject { post :reject, params: { id: account.id } }
let(:current_user) { Fabricate(:user, role: role) }
let(:account) { user.account }
let(:user) { Fabricate(:user) }
before do
account.user.update(approved: false)
end
context 'when user is admin' do
let(:role) { UserRole.find_by(name: 'Admin') }
it 'succeeds in rejecting account' do
is_expected.to redirect_to admin_accounts_path(status: 'pending')
end
it 'logs action' do
is_expected.to have_http_status :found
log_item = Admin::ActionLog.last
expect(log_item).to_not be_nil
expect(log_item.action).to eq :reject
expect(log_item.account_id).to eq current_user.account_id
expect(log_item.target_id).to eq account.user.id
end
end
context 'when user is not admin' do
let(:role) { UserRole.everyone }
it 'fails to reject account' do
is_expected.to have_http_status :forbidden
expect(user.reload).not_to be_approved
end
end
end
describe 'POST #redownload' do describe 'POST #redownload' do
subject { post :redownload, params: { id: account.id } } subject { post :redownload, params: { id: account.id } }

View file

@ -100,6 +100,15 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
it 'approves user' do it 'approves user' do
expect(account.reload.user_approved?).to be true expect(account.reload.user_approved?).to be true
end end
it 'logs action' do
log_item = Admin::ActionLog.last
expect(log_item).to_not be_nil
expect(log_item.action).to eq :approve
expect(log_item.account_id).to eq user.account_id
expect(log_item.target_id).to eq account.user.id
end
end end
describe 'POST #reject' do describe 'POST #reject' do
@ -118,6 +127,15 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
it 'removes user' do it 'removes user' do
expect(User.where(id: account.user.id).count).to eq 0 expect(User.where(id: account.user.id).count).to eq 0
end end
it 'logs action' do
log_item = Admin::ActionLog.last
expect(log_item).to_not be_nil
expect(log_item.action).to eq :reject
expect(log_item.account_id).to eq user.account_id
expect(log_item.target_id).to eq account.user.id
end
end end
describe 'POST #enable' do describe 'POST #enable' do

View file

@ -22,9 +22,11 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
describe 'POST #create' do describe 'POST #create' do
let(:scopes) { 'write:filters' } let(:scopes) { 'write:filters' }
let(:irreversible) { true }
let(:whole_word) { false }
before do before do
post :create, params: { phrase: 'magic', context: %w(home), irreversible: true } post :create, params: { phrase: 'magic', context: %w(home), irreversible: irreversible, whole_word: whole_word }
end end
it 'returns http success' do it 'returns http success' do
@ -34,11 +36,29 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
it 'creates a filter' do it 'creates a filter' do
filter = user.account.custom_filters.first filter = user.account.custom_filters.first
expect(filter).to_not be_nil expect(filter).to_not be_nil
expect(filter.keywords.pluck(:keyword)).to eq ['magic'] expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]]
expect(filter.context).to eq %w(home) expect(filter.context).to eq %w(home)
expect(filter.irreversible?).to be true expect(filter.irreversible?).to be irreversible
expect(filter.expires_at).to be_nil expect(filter.expires_at).to be_nil
end end
context 'with different parameters' do
let(:irreversible) { false }
let(:whole_word) { true }
it 'returns http success' do
expect(response).to have_http_status(200)
end
it 'creates a filter' do
filter = user.account.custom_filters.first
expect(filter).to_not be_nil
expect(filter.keywords.pluck(:keyword, :whole_word)).to eq [['magic', whole_word]]
expect(filter.context).to eq %w(home)
expect(filter.irreversible?).to be irreversible
expect(filter.expires_at).to be_nil
end
end
end end
describe 'GET #show' do describe 'GET #show' do

View file

@ -1,5 +1,48 @@
require 'rails_helper' require 'rails_helper'
RSpec.describe AccountMigration, type: :model do RSpec.describe AccountMigration, type: :model do
describe 'validations' do
let(:source_account) { Fabricate(:account) }
let(:target_acct) { target_account.acct }
let(:subject) { AccountMigration.new(account: source_account, acct: target_acct) }
context 'with valid properties' do
let(:target_account) { Fabricate(:account, username: 'target', domain: 'remote.org') }
before do
target_account.aliases.create!(acct: source_account.acct)
service_double = double
allow(ResolveAccountService).to receive(:new).and_return(service_double)
allow(service_double).to receive(:call).with(target_acct, anything).and_return(target_account)
end
it 'passes validations' do
expect(subject).to be_valid
end
end
context 'with unresolveable account' do
let(:target_acct) { 'target@remote' }
before do
service_double = double
allow(ResolveAccountService).to receive(:new).and_return(service_double)
allow(service_double).to receive(:call).with(target_acct, anything).and_return(nil)
end
it 'has errors on acct field' do
expect(subject).to model_have_error_on_field(:acct)
end
end
context 'with a space in the domain part' do
let(:target_acct) { 'target@remote. org' }
it 'has errors on acct field' do
expect(subject).to model_have_error_on_field(:acct)
end
end
end
end end