merge catstodon/main into main

main
anna 1 year ago
commit 380567f453
Signed by: fef
GPG Key ID: EC22E476DC2D3D84

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

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

71
Vagrantfile vendored

@ -3,16 +3,14 @@
ENV["PORT"] ||= "3000"
$provision = <<SCRIPT
cd /vagrant # This is where the host folder/repo is mounted
$provisionA = <<SCRIPT
# Add the yarn repo + yarn repo keys
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'
# 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
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 \
postgresql \
postgresql-contrib \
yarn \
libicu-dev \
libidn11-dev \
libreadline-dev \
libpam0g-dev \
libreadline6-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
# 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
curl -sSL https://rvm.io/pkuczynski.asc | gpg --import
sudo usermod -a -G rvm $USER
SCRIPT
curl -sSL https://raw.githubusercontent.com/rvm/rvm/stable/binscripts/rvm-installer | bash -s stable --ruby=$RUBY_VERSION
source /home/vagrant/.rvm/scripts/rvm
$provisionB = <<SCRIPT
source "/etc/profile.d/rvm.sh"
# Install Ruby
rvm reinstall ruby-$RUBY_VERSION --disable-binary
read RUBY_VERSION < /vagrant/.ruby-version
rvm install ruby-$RUBY_VERSION --disable-binary
# Configure database
sudo -u postgres createuser -U postgres vagrant -s
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
bundle install
# Install node modules
sudo corepack enable
yarn set version classic
yarn install
# Build Mastodon
@ -72,18 +94,11 @@ echo 'export $(cat "/vagrant/.env.vagrant" | xargs)' >> ~/.bash_profile
SCRIPT
$start = <<SCRIPT
echo 'To start server'
echo ' $ vagrant ssh -c "cd /vagrant && foreman start"'
SCRIPT
VAGRANTFILE_API_VERSION = "2"
Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.box = "ubuntu/focal64"
config.vm.provider :virtualbox do |vb|
vb.name = "mastodon"
@ -100,7 +115,6 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
# Use "virtio" network interfaces for better performance.
vb.customize ["modifyvm", :id, "--nictype1", "virtio"]
vb.customize ["modifyvm", :id, "--nictype2", "virtio"]
end
# This uses the vagrant-hostsupdater plugin, and lets you
@ -118,7 +132,7 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
end
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
config.vm.synced_folder ".", "/vagrant"
end
@ -129,9 +143,12 @@ Vagrant.configure(VAGRANTFILE_API_VERSION) do |config|
config.vm.network :forwarded_port, guest: 8080, host: 8080
# 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.provision :shell, inline: $start, run: 'always', privileged: false
config.vm.post_up_message = <<MESSAGE
To start server
$ vagrant ssh -c "cd /vagrant && foreman start"
MESSAGE
end

@ -55,12 +55,14 @@ module Admin
def approve
authorize @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)
end
def reject
authorize @account.user, :reject?
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)
end

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

@ -13,7 +13,7 @@ class Api::V1::FiltersController < Api::BaseController
def create
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)
end
@ -52,11 +52,11 @@ class Api::V1::FiltersController < Api::BaseController
end
def resource_params
params.permit(:phrase, :expires_in, :irreversible, context: [])
params.permit(:phrase, :expires_in, :irreversible, :whole_word, context: [])
end
def filter_params
resource_params.slice(:expires_in, :irreversible, :context)
resource_params.slice(:phrase, :expires_in, :irreversible, :context)
end
def keyword_params

@ -7,6 +7,7 @@ import Avatar from 'flavours/glitch/components/avatar';
import Permalink from 'flavours/glitch/components/permalink';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { openModal } from 'flavours/glitch/actions/modal';
const Account = connect(state => ({
account: state.getIn(['accounts', me]),
@ -16,7 +17,14 @@ const Account = connect(state => ({
</Permalink>
));
export default @withRouter
const mapDispatchToProps = (dispatch) => ({
openClosedRegistrationsModal() {
dispatch(openModal('CLOSED_REGISTRATIONS'));
},
});
export default @connect(null, mapDispatchToProps)
@withRouter
class Header extends React.PureComponent {
static contextTypes = {
@ -24,12 +32,13 @@ class Header extends React.PureComponent {
};
static propTypes = {
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
};
render () {
const { signedIn } = this.context.identity;
const { location } = this.props;
const { location, openClosedRegistrationsModal } = this.props;
let content;
@ -41,10 +50,26 @@ class Header extends React.PureComponent {
</>
);
} 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 = (
<>
<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}
</>
);
}

@ -1,7 +1,180 @@
import inherited from 'mastodon/locales/cs.json';
const messages = {
// No translations available.
'about.fork_disclaimer': 'Glitch-soc je svobodný software s otevřeným zdrojovým kódem založený na Mastodonu.',
'settings.layout_opts': 'Možnosti rozvržení',
'settings.layout': 'Rozložení:',
'layout.current_is': 'Nastavené rozložení je:',
'layout.auto': 'Automatické',
'layout.desktop': 'Desktop',
'layout.mobile': 'Mobil',
'layout.hint.auto': 'Vybrat rozložení automaticky v závislosti na nastavení “Povolit pokročilé webové rozhraní” a velikosti obrazovky.',
'layout.hint.desktop': 'Použít vícesloupcové rozložení nezávisle na nastavení “Povolit pokročilé webové rozhraní” a velikosti obrazovky.',
'layout.hint.single': 'Použít jednosloupcové rozložení nezávisle na nastavení “Povolit pokročilé webové rozhraní” a velikosti obrazovky.',
'navigation_bar.app_settings': 'Nastavení aplikace',
'navigation_bar.featured_users': 'Vybraní uživatelé',
'endorsed_accounts_editor.endorsed_accounts': 'Vybrané účty',
'navigation_bar.info': 'Rozšířené informace',
'navigation_bar.misc': 'Různé',
'navigation_bar.keyboard_shortcuts': 'Klávesové zkratky',
'getting_started.onboarding': 'Ukaž mi to tu',
'onboarding.skip': 'Přeskočit',
'onboarding.next': 'Další',
'onboarding.done': 'Hotovo',
'onboarding.page_one.federation': '{domain} je \'instance\' Mastodonu. Mastodon je síť nezávislých serverů, které jsou spolu propojené do jedné velké sociální sítě. Těmto serverům říkáme instance.',
'onboarding.page_one.handle': 'Jste na instanci {domain}, takže celá adresa vašeho profilu je {handle}',
'onboarding.page_one.welcome': 'Vítá vás {domain}!',
'onboarding.page_two.compose': 'Příspěvky se píší v levém sloupci. Pomocí ikon pod příspěvkem k němu můžete připojit obrázky, změnit úroveň soukromí nebo přidat varování o obsahu.',
'onboarding.page_three.search': 'Pomocí vyhledávací lišty můžete hledat lidi nebo hashtagy. Pokud hledáte někoho z jiné instance, musíte použít celou adresu jeho profilu.',
'onboarding.page_three.profile': 'Upravte si svůj profil a nastavte si profilový obrázek, jméno, a krátký text o sobě. Naleznete tam i další možnosti nastavení.',
'onboarding.page_four.home': 'Domovská časová osa zobrazuje příspěvky od lidí, které sledujete.',
'onboarding.page_four.notifications': 'Notifikace se zobrazí, když s vámi někdo interaguje.',
'onboarding.page_five.public_timelines': 'Místní časová osa zobrazuje veřejné příspěvky všech uživatelů instance {domain}. Federovaná časová osa zobrazí příspěvky od všech, koho uživatelé instance {domain} sledují. Tyto veřejné časové osy jsou skvělý způsob, jak objevit nové lidi.',
'onboarding.page_six.almost_done': 'Skoro hotovo...',
'onboarding.page_six.github': 'Na serveru {domain} běží Glitchsoc. Glitchsoc je přátelský {fork} programu {Mastodon}, a je kompatibilní s jakoukoliv jinou mastodoní instancí nebo aplikací. Glitchsoc je zcela svobodný a má otevřený zdrojový kód. Na stránce {github} můžete hlásit chyby, žádat o nové funkce, nebo ke kódu vlastnoručně přispět.',
'onboarding.page_six.apps_available': 'Jsou dostupné {apps} pro iOS, Android i jiné platformy.',
'onboarding.page_six.various_app': 'mobilní aplikace',
'onboarding.page_six.appetoot': 'Veselé mastodonění!',
'settings.auto_collapse': 'Automaticky sbalit',
'settings.auto_collapse_all': 'Všechno',
'settings.auto_collapse_lengthy': 'Dlouhé příspěvky',
'settings.auto_collapse_media': 'Příspěvky s přílohami',
'settings.auto_collapse_notifications': 'Oznámení',
'settings.auto_collapse_reblogs': 'Boosty',
'settings.auto_collapse_replies': 'Odpovědi',
'settings.show_action_bar': 'Zobrazit ve sbalených příspěvcích tlačítka s akcemi',
'settings.close': 'Zavřít',
'settings.collapsed_statuses': 'Sbalené příspěvky',
'settings.confirm_boost_missing_media_description': 'Zobrazit potvrzovací dialog před boostnutím příspěvku s chybějícími popisky obrázků',
'boost_modal.missing_description': 'Příspěvek obsahuje obrázky bez popisků',
'settings.enable_collapsed': 'Povolit sbalené příspěvky',
'settings.enable_collapsed_hint': 'U sbalených příspěvků je část jejich obsahu skrytá, aby zabraly méně místa na obrazovce. (Tohle není stejná funkce jako varování o obsahu.)',
'settings.general': 'Obecné',
'settings.hicolor_privacy_icons': 'Barevné ikony soukromí',
'settings.hicolor_privacy_icons.hint': 'Zobrazit ikony úrovně soukromí příspěvků v jasných, snadno rozlišitelných barvách',
'settings.image_backgrounds': 'Obrázkové pozadí',
'settings.image_backgrounds_media': 'Náhled médií ve sbalených příspěvcích',
'settings.image_backgrounds_media_hint': 'Pokud jsou k příspěvku přiložena média, použije se první z nich jako pozadí',
'settings.image_backgrounds_users': 'Nastavit sbaleným příspěvkům obrázkové pozadí',
'settings.inline_preview_cards': 'Zobrazit v časové ose náhledy externích odkazů',
'settings.media': 'Média',
'settings.media_letterbox': 'Neořezávat obrázky',
'settings.media_letterbox_hint': 'Místo výřezu obrázku zobrazit obrázek celý, doplněný podle potřeby o prázdné okraje',
'settings.media_fullwidth': 'Zobrazit náhledy v plné šířce',
'settings.notifications_opts': 'Možnosti oznámení',
'settings.notifications.tab_badge': 'Zobrazit počet nepřečtených oznámení',
'settings.notifications.tab_badge.hint': 'Počet nepřečtených oznámení se viditelně zobrazí na hlavní stránce (pokud není seznam oznámení viditelný)',
'settings.notifications.favicon_badge': 'Zobrazit počet na ikoně serveru',
'settings.notifications.favicon_badge.hint': 'Zobrazí počet nepřečtených oznámení na ikoně serveru',
'settings.preferences': 'Předvolby',
'settings.rewrite_mentions': 'Přepsat zmínky v zobrazených příspěvcích',
'settings.rewrite_mentions_no': 'Nepřepisovat zmínky',
'settings.rewrite_mentions_acct': 'Přepsat uživatelským jménem a doménou (pokud je účet na jiném serveru)',
'settings.rewrite_mentions_username': 'Přepsat uživatelským jménem',
'settings.show_reply_counter': 'Zobrazit odhad počtu odpovědí',
'settings.status_icons': 'Ikony u příspěvků',
'settings.status_icons_language': 'Indikace jazyk',
'settings.status_icons_reply': 'Indikace odpovědi',
'settings.status_icons_local_only': 'Indikace lokálního příspěvku',
'settings.status_icons_media': 'Indikace obrázků a anket',
'settings.status_icons_visibility': 'Indikace úrovně soukromí',
'settings.tag_misleading_links': 'Označit zavádějící odkazy',
'settings.tag_misleading_links.hint': 'Zobrazit skutečný cíl u každého odkazu, který ho explicitně nezmiňuje',
'settings.wide_view': 'Široké sloupce (pouze v režimu Desktop)',
'settings.wide_view_hint': 'Sloupce se roztáhnout, aby lépe vyplnily dostupný prostor.',
'settings.navbar_under': 'Navigační lišta vespod (pouze v režimu Mobil)',
'settings.compose_box_opts': 'Editační pole',
'settings.always_show_spoilers_field': 'Vždy zobrazit pole pro varování o obsahu',
'settings.prepend_cw_re': 'Při odpovídání přidat před varování o obsahu “re: ”',
'settings.preselect_on_reply': 'Při odpovědi označit uživatelská jména',
'settings.preselect_on_reply_hint': 'Při odpovídání na konverzaci s více účastníky se jména všech kromě prvního označí, aby šla jednoduše smazat',
'settings.confirm_missing_media_description': 'Zobrazit potvrzovací dialog při odesílání příspěvku, ve kterém chybí popisky obrázků',
'settings.confirm_before_clearing_draft': 'Zobrazit potvrzovací dialog před přepsáním právě vytvářené zprávy',
'settings.show_content_type_choice': 'Zobrazit volbu formátu příspěvku',
'settings.side_arm': 'Vedlejší odesílací tlačítko:',
'settings.side_arm.none': 'Žádné',
'settings.side_arm_reply_mode': 'Při odpovídání na příspěvek by vedlejší odesílací tlačítko mělo:',
'settings.side_arm_reply_mode.keep': 'Použít svou nastavenou úroveň soukromí',
'settings.side_arm_reply_mode.copy': 'Použít úroveň soukromí příspěvku, na který odpovídáte',
'settings.side_arm_reply_mode.restrict': 'Zvýšit úroveň soukromí nejméně na úroveň příspěvku, na který odpovídáte',
'settings.content_warnings': 'Varování o obsahu',
'settings.content_warnings_shared_state': 'Zobrazit/schovat všechny kopie naráz',
'settings.content_warnings_shared_state_hint': 'Tlačítko varování o obsahu bude mít efekt na všechny kopie příspěvku naráz, stejně jako na běžném Mastodonu. Nebude pak možné automaticky sbalit jakoukoliv kopii příspěvku, která má rozbalené varování o obsahu',
'settings.content_warnings_media_outside': 'Zobrazit obrázky a videa mimo varování o obsahu',
'settings.content_warnings_media_outside_hint': 'Obrázky a videa z příspěvku s varováním o obsahu se zobrazí se separátním přepínačem zobrazení, stejně jako na běžném Mastodonu.',
'settings.content_warnings_unfold_opts': 'Možnosti automatického rozbalení',
'settings.enable_content_warnings_auto_unfold': 'Vždy rozbalit příspěvky označené varováním o obsahu',
'settings.deprecated_setting': 'Tato možnost se nyní nastavuje v {settings_page_link}',
'settings.shared_settings_link': 'předvolbách Mastodonu',
'settings.content_warnings_filter': 'Tato varování o obsahu automaticky nerozbalovat:',
'settings.content_warnings.regexp': 'Regulární výraz',
'settings.media_reveal_behind_cw': 'Automaticky zobrazit média označená varováním o obsahu',
'settings.pop_in_player': 'Povolit plovoucí okno přehrávače',
'settings.pop_in_position': 'Pozice plovoucího okna:',
'settings.pop_in_left': 'Vlevo',
'settings.pop_in_right': 'Vpravo',
'status.collapse': 'Sbalit',
'status.uncollapse': 'Rozbalit',
'status.in_reply_to': 'Tento příspěvek je odpověď',
'status.has_preview_card': 'Obsahuje náhled odkazu',
'status.has_pictures': 'Obsahuje obrázky',
'status.is_poll': 'Tento příspěvek je anketa',
'status.has_video': 'Obsahuje video',
'status.has_audio': 'Obsahuje audio',
'status.local_only': 'Viditelné pouze z vaší instance',
'media_gallery.sensitive': 'Citlivý obsah',
'favourite_modal.combo': 'Příště můžete pro přeskočení stisknout {combo}',
'home.column_settings.show_direct': 'Zobrazit přímé zprávy',
'notification_purge.start': 'Čistící režim',
'notifications.mark_as_read': 'Označit všechna oznámení jako přečtená',
'notification.markForDeletion': 'Označit pro smazání',
'notifications.clear': 'Vymazat všechna oznámení',
'notifications.marked_clear_confirmation': 'Určitě chcete trvale smazat všechna vybraná oznámení?',
'notifications.marked_clear': 'Smazat vybraná oznámení',
'notification_purge.btn_all': 'Vybrat\nvše',
'notification_purge.btn_none': 'Nevybrat\nnic',
'notification_purge.btn_invert': 'Obrátit\nvýběr',
'notification_purge.btn_apply': 'Smazat\nvybrané',
'compose.attach.upload': 'Nahrát soubor',
'compose.attach.doodle': 'Něco namalovat',
'compose.attach': 'Připojit...',
'advanced_options.local-only.short': 'Lokální příspěvek',
'advanced_options.local-only.long': 'Neposílat na jiné servery',
'advanced_options.local-only.tooltip': 'Tento příspěvek je pouze lokální',
'advanced_options.icon_title': 'Pokročilá nastavení',
'advanced_options.threaded_mode.short': 'Režim vlákna',
'advanced_options.threaded_mode.long': 'Po odeslání automaticky otevře pole pro odpověď',
'advanced_options.threaded_mode.tooltip': 'Režim vlákna je zapnutý',
'home.column_settings.advanced': 'Pokročilé',
'home.column_settings.filter_regex': 'Filtrovat podle regulárních výrazů',
'compose_form.poll.single_choice': 'Povolit jednu odpověď',
'compose_form.poll.multiple_choices': 'Povolit více odpovědí',
'compose.content-type.plain': 'Prostý text',
'content-type.change': 'Formát příspěvku',
'compose_form.spoiler': 'Přidat varování o obsahu',
'direct.group_by_conversations': 'Seskupit do konverzací',
'column.toot': 'Příspěvky a odpovědi',
'confirmation_modal.do_not_ask_again': 'Příště se už neptat',
'keyboard_shortcuts.bookmark': 'Přidat do záložek',
'keyboard_shortcuts.toggle_collapse': 'Sbalit/rozbalit příspěvek',
'keyboard_shortcuts.secondary_toot': 'Odeslat příspěvek s druhotným nastavením soukromí',
'column.subheading': 'Různé',
};
export default Object.assign({}, inherited, messages);

@ -6,6 +6,14 @@ en:
skins:
glitch:
default: Default
cs:
flavours:
glitch:
description: Výchozí rozhraní instancí GlitchSoc.
name: Glitch
skins:
glitch:
default: Výchozí
pl:
flavours:
glitch:

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

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

@ -6,6 +6,14 @@ en:
skins:
vanilla:
default: Default
cs:
flavours:
vanilla:
description: Standardní rozhraní Mastodonu. Některé funkce GlitchSoc v něm nejsou podporované.
name: Standardní Mastodon
skins:
vanilla:
default: Výchozí
pl:
flavours:
vanilla:

@ -6,6 +6,7 @@ import { registrationsOpen, me } from 'mastodon/initial_state';
import Avatar from 'mastodon/components/avatar';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { openModal } from 'mastodon/actions/modal';
const Account = connect(state => ({
account: state.getIn(['accounts', me]),
@ -15,7 +16,14 @@ const Account = connect(state => ({
</Link>
));
export default @withRouter
const mapDispatchToProps = (dispatch) => ({
openClosedRegistrationsModal() {
dispatch(openModal('CLOSED_REGISTRATIONS'));
},
});
export default @connect(null, mapDispatchToProps)
@withRouter
class Header extends React.PureComponent {
static contextTypes = {
@ -23,12 +31,13 @@ class Header extends React.PureComponent {
};
static propTypes = {
openClosedRegistrationsModal: PropTypes.func,
location: PropTypes.object,
};
render () {
const { signedIn } = this.context.identity;
const { location } = this.props;
const { location, openClosedRegistrationsModal } = this.props;
let content;
@ -40,10 +49,26 @@ class Header extends React.PureComponent {
</>
);
} 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 = (
<>
<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}
</>
);
}

@ -2,6 +2,10 @@ en:
skins:
glitch:
contrast: High contrast
cs:
skins:
glitch:
contrast: Vysoký kontrast
es:
skins:
glitch:

@ -2,6 +2,10 @@ en:
skins:
glitch:
mastodon-light: Mastodon (light)
cs:
skins:
glitch:
mastodon-light: Mastodon (světlý)
es:
skins:
glitch:

@ -2,6 +2,10 @@ en:
skins:
vanilla:
contrast: High contrast
cs:
skins:
vanilla:
contrast: Vysoký kontrast
es:
skins:
vanilla:

@ -2,6 +2,10 @@ en:
skins:
vanilla:
mastodon-light: Mastodon (light)
cs:
skins:
vanilla:
mastodon-light: Mastodon (světlý)
es:
skins:
glitch:

@ -2,3 +2,7 @@ en:
skins:
vanilla:
win95: 95
cs:
skins:
vanilla:
win95: Windows 95

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

@ -10,7 +10,7 @@ class DeliveryFailureTracker
end
def track_failure!
redis.sadd(exhausted_deliveries_key, today)
redis.sadd?(exhausted_deliveries_key, today)
UnavailableDomain.create(domain: @host) if reached_failure_threshold?
end

@ -322,24 +322,24 @@ class FeedManager
def clean_feeds!(type, ids)
reblogged_id_sets = {}
redis.pipelined do
redis.pipelined do |pipeline|
ids.each do |feed_id|
redis.del(key(type, feed_id))
pipeline.del(key(type, feed_id))
reblog_key = key(type, feed_id, 'reblogs')
# We collect a future for this: we don't block while getting
# it, but we can iterate over it later.
reblogged_id_sets[feed_id] = redis.zrange(reblog_key, 0, -1)
redis.del(reblog_key)
reblogged_id_sets[feed_id] = pipeline.zrange(reblog_key, 0, -1)
pipeline.del(reblog_key)
end
end
# Remove all of the reblog tracking keys we just removed the
# references to.
redis.pipelined do
redis.pipelined do |pipeline|
reblogged_id_sets.each do |feed_id, future|
future.value.each do |reblogged_id|
reblog_set_key = key(type, feed_id, "reblogs:#{reblogged_id}")
redis.del(reblog_set_key)
pipeline.del(reblog_set_key)
end
end
end
@ -519,7 +519,7 @@ class FeedManager
# REBLOG_FALLOFF most recent statuses, so we note that this
# is an "extra" reblog, by storing it in reblog_set_key.
reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
redis.sadd(reblog_set_key, status.id)
redis.sadd?(reblog_set_key, status.id)
return false
end
else
@ -556,7 +556,7 @@ class FeedManager
# 2. Remove reblog from set of this status's reblogs.
reblog_set_key = key(timeline_type, account_id, "reblogs:#{status.reblog_of_id}")
redis.srem(reblog_set_key, status.id)
redis.srem?(reblog_set_key, status.id)
redis.zrem(reblog_key, status.reblog_of_id)
# 3. Re-insert another reblog or original into the feed if one
# remains in the set. We could pick a random element, but this

@ -42,6 +42,6 @@ class Vacuum::StatusesVacuum
end
def remove_from_search_index(status_ids)
with_redis { |redis| redis.sadd('chewy:queue:StatusesIndex', status_ids) }
with_redis { |redis| redis.sadd?('chewy:queue:StatusesIndex', status_ids) }
end
end

@ -59,7 +59,7 @@ class AccountMigration < ApplicationRecord
def set_target_account
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
end

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

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

@ -19,9 +19,9 @@ class FollowRecommendationSuppression < ApplicationRecord
private
def remove_follow_recommendations
redis.pipelined do
redis.pipelined do |pipeline|
I18n.available_locales.each do |locale|
redis.zrem("follow_recommendations:#{locale}", account_id)
pipeline.zrem("follow_recommendations:#{locale}", account_id)
end
end
end

@ -32,7 +32,7 @@ class Form::Redirect
def set_target_account
@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
end

@ -60,7 +60,7 @@ class Trends::Base
end
def record_used_id(id, at_time = Time.now.utc)
redis.sadd(used_key(at_time), id)
redis.sadd?(used_key(at_time), id)
redis.expire(used_key(at_time), 1.day.seconds)
end

@ -48,9 +48,9 @@ class BatchedRemoveStatusService < BaseService
# Cannot be batched
@status_id_cutoff = Mastodon::Snowflake.id_at(2.weeks.ago)
redis.pipelined do
redis.pipelined do |pipeline|
statuses.each do |status|
unpush_from_public_timelines(status)
unpush_from_public_timelines(pipeline, status)
end
end
end
@ -73,22 +73,22 @@ class BatchedRemoveStatusService < BaseService
end
end
def unpush_from_public_timelines(status)
def unpush_from_public_timelines(pipeline, status)
return unless status.public_visibility? && status.id > @status_id_cutoff
payload = Oj.dump(event: :delete, payload: status.id.to_s)
redis.publish('timeline:public', payload)
redis.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload)
pipeline.publish('timeline:public', payload)
pipeline.publish(status.local? ? 'timeline:public:local' : 'timeline:public:remote', payload)
if status.media_attachments.any?
redis.publish('timeline:public:media', payload)
redis.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
pipeline.publish('timeline:public:media', payload)
pipeline.publish(status.local? ? 'timeline:public:local:media' : 'timeline:public:remote:media', payload)
end
status.tags.map { |tag| tag.name.mb_chars.downcase }.each do |hashtag|
redis.publish("timeline:hashtag:#{hashtag}", payload)
redis.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
pipeline.publish("timeline:hashtag:#{hashtag}", payload)
pipeline.publish("timeline:hashtag:#{hashtag}:local", payload) if status.local?
end
end

@ -16,7 +16,7 @@ class Scheduler::IndexingScheduler
type.import!(ids)
redis.pipelined do |pipeline|
ids.each { |id| pipeline.srem("chewy:queue:#{type.name}", id) }
ids.each { |id| pipeline.srem?("chewy:queue:#{type.name}", id) }
end
end
end

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

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

@ -0,0 +1,41 @@
---
cs:
admin:
custom_emojis:
batch_copy_error: 'Při kopírování některých emoji došlo k chybě: %{message}'
batch_error: 'Došlo k chybě: %{message}'
settings:
captcha_enabled:
desc_html: Tato funkce používá externí skripty služby hCaptcha, což může být problém z hlediska bezpečí a ochrany dat. Také to může <strong>některým (hlavně postiženým) lidem registrační proces výrazně zkomplikovat</strong>. Z tohoto důvodu prosím raději zvažte jiné možnosti, jako je schvalování registrací nebo registrace pouze pro zvané.<br>Uživatelům pozvaným skrze omezenou pozvánku se CAPTCHA nezobrazí.
title: Vyžadovat po nových uživatelích opsání textu z obrázku (CAPTCHA)
enable_keybase:
desc_html: Uživatelé budou moci potvrdit svou identitu pomocí Keybase
title: Zapnout potvrzování pomocí Keybase
flavour_and_skin:
title: Rozhraní a styl
other:
preamble: Různá nastavení glitch-soc, která se nevešla do jiných kategorií.
title: Jiné
outgoing_spoilers:
desc_html: Při federování příspěvků se přidá toto varování o obsahu příspěvkům, které žádné nemají. To může být užitečné, pokud je váš server zaměřen na specifický obsah, pro který by jiné servery mohly varování o obsahu vyžadovat. Připojená média budou označena jako citlivá.
title: Varování o obsahu pro odesílané příspěvky
hide_followers_count:
desc_html: Nezobrazovat na uživatelských profilech počet sledujících
title: Schovat počet sledujících
show_reblogs_in_public_timelines:
desc_html: Veřejné boosty veřejných příspěvků se zobrazí na místní a federované časové ose.
title: Zobrazovat ve veřejných časových osách boosty
show_replies_in_public_timelines:
desc_html: Na místní a federované časové ose se kromě odpovědí autora na vlastní příspěvky (vláken) zobrazí i ostatní veřejné odpovědi.
title: Zobrazovat ve veřejných časových osách odpovědi
trending_status_cw:
desc_html: Zobrazovat v rámci trendů (pokud jsou zapnuté) i příspěvky s varováním o obsahu. Změny tohoto nastavení se neprojeví retroaktivně.
title: Povolit v trendech příspěvky s varováním o obsahu
auth:
captcha_confirmation:
hint_html: Už jen poslední krok! Pro potvrzení svého účtu opište prosím text z obrázku (CAPTCHA). Pokud máte dotazy nebo potřebujete s potvrzením pomoct, můžete <a href="/about/more">kontaktovat administrátora</a>.
title: Ověření uživatele
generic:
use_this: Použít
settings:
flavours: Rozhraní

@ -0,0 +1,27 @@
---
cs:
simple_form:
glitch_only: glitch-soc
hints:
defaults:
fields: Na svém profilu můžete mít zobrazeno několik položek (max. %{count}) jako tabulku
setting_default_content_type_html: Předpokládat, že nové příspěvky jsou napsané v HTML, pokud není uvedeno jinak
setting_default_content_type_markdown: Předpokládat, že nové příspěvky používají pro formátování Markdown, pokud není uvedeno jinak
setting_default_content_type_plain: Předpokládat, že nové příspěvky nejsou nijak formátované, pokud není uvedeno jinak (standardní chování Mastodonu)
setting_default_language: Jazyk vašich příspěvků lze detekovat automaticky, ale není to vždycky přesné
setting_hide_followers_count: Počet vašich sledujících se nebude nikomu zobrazovat, ani vám. Některé aplikace mohou zobrazit negativní počet sledujících.
setting_skin: Aplikuje barevný styl na zvolené rozhraní Mastodonu
labels:
defaults:
setting_default_content_type: Výchozí formát příspěvků
setting_default_content_type_html: HTML
setting_default_content_type_markdown: Markdown
setting_default_content_type_plain: Prostý text
setting_favourite_modal: Před oblíbením příspěvku zobrazit potvrzovací dialog (pouze pro rozhraní Glitch)
setting_hide_followers_count: Skrýt počet vašich sledujících
setting_skin: Styl
setting_system_emoji_font: Použít výchozí emoji systému (pouze pro rozhraní Glitch)
notification_emails:
trending_tag: Nový populární hashtag vyžaduje schválení
trending_link: Nový populární odkaz vyžaduje schválení
trending_status: Nový populární příspěvek vyžaduje schválení

@ -79,69 +79,72 @@ class BackfillAdminActionLogs < ActiveRecord::Migration[6.1]
safety_assured do
AdminActionLog.includes(:account).where(target_type: 'Account', human_identifier: nil).find_each do |log|
next if log.account.nil?
log.update(human_identifier: log.account.acct)
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(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
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(human_identifier: log.domain_block.domain)
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(human_identifier: log.domain_allow.domain)
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(human_identifier: log.email_domain_block.domain)
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(human_identifier: log.unavailable_domain.domain)
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(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
AdminActionLog.includes(account_warning: :account).where(target_type: 'AccountWarning', human_identifier: nil).find_each do |log|
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
AdminActionLog.includes(:announcement).where(target_type: 'Announcement', human_identifier: nil).find_each do |log|
next if log.announcement.nil?
log.update(human_identifier: log.announcement.text)
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(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
AdminActionLog.includes(:custom_emoji).where(target_type: 'CustomEmoji', human_identifier: nil).find_each do |log|
next if log.custom_emoji.nil?
log.update(human_identifier: log.custom_emoji.shortcode)
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(human_identifier: log.canonical_email_block.canonical_email_hash)
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(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

@ -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

@ -10,7 +10,7 @@
#
# It's strongly recommended that you check this file into your version control system.
ActiveRecord::Schema.define(version: 2022_11_24_114030) do
ActiveRecord::Schema.define(version: 2022_12_06_114142) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@ -781,16 +781,6 @@ ActiveRecord::Schema.define(version: 2022_11_24_114030) do
t.index ["status_id", "preview_card_id"], name: "index_preview_cards_statuses_on_status_id_and_preview_card_id"
end
create_table "reactions", force: :cascade do |t|
t.string "emoji"
t.bigint "status_id", null: false
t.bigint "account_id", null: false
t.datetime "created_at", precision: 6, null: false
t.datetime "updated_at", precision: 6, null: false
t.index ["account_id"], name: "index_reactions_on_account_id"
t.index ["status_id"], name: "index_reactions_on_status_id"
end
create_table "relays", force: :cascade do |t|
t.string "inbox_url", default: "", null: false
t.string "follow_activity_id"
@ -1221,8 +1211,6 @@ ActiveRecord::Schema.define(version: 2022_11_24_114030) do
add_foreign_key "polls", "accounts", on_delete: :cascade
add_foreign_key "polls", "statuses", on_delete: :cascade
add_foreign_key "preview_card_trends", "preview_cards", on_delete: :cascade
add_foreign_key "reactions", "accounts"
add_foreign_key "reactions", "statuses"
add_foreign_key "report_notes", "accounts", on_delete: :cascade
add_foreign_key "report_notes", "reports", on_delete: :cascade
add_foreign_key "reports", "accounts", column: "action_taken_by_account_id", name: "fk_bca45b75fd", on_delete: :nullify

@ -17,7 +17,7 @@ module Chewy
RedisConfiguration.with do |redis|
redis.pipelined do |pipeline|
@stash.each do |type, ids|
pipeline.sadd("chewy:queue:#{type.name}", ids)
pipeline.sadd?("chewy:queue:#{type.name}", ids)
end
end
end

@ -54,8 +54,8 @@ module Mastodon
def clear
keys = redis.keys('feed:*')
redis.pipelined do
keys.each { |key| redis.del(key) }
redis.pipelined do |pipeline|
keys.each { |key| pipeline.del(key) }
end
say('OK', :green)

@ -25,7 +25,7 @@ module Mastodon
end
def suffix_version
'+1.0.10'
'+1.0.11'
end
def post_suffix

@ -43,6 +43,16 @@ namespace :tests do
puts 'CustomFilterKeyword records not created as expected'
exit(1)
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
desc 'Populate the database with test data for 2.4.3'
@ -84,8 +94,8 @@ namespace :tests do
VALUES
(1, 'destroy', 'Account', 1, now(), now()),
(1, 'destroy', 'User', 1, now(), now()),
(1, 'destroy', 'DomainBlock', 1312, now(), now()),
(1, 'destroy', 'EmailDomainBlock', 1312, now(), now()),
(1, 'destroy', 'DomainBlock', 1, now(), now()),
(1, 'destroy', 'EmailDomainBlock', 1, now(), now()),
(1, 'destroy', 'Status', 1, now(), now()),
(1, 'destroy', 'CustomEmoji', 3, now(), now());
SQL

@ -147,6 +147,87 @@ RSpec.describe Admin::AccountsController, type: :controller do
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
subject { post :redownload, params: { id: account.id } }

@ -100,6 +100,15 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
it 'approves user' do
expect(account.reload.user_approved?).to be true
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
describe 'POST #reject' do
@ -118,6 +127,15 @@ RSpec.describe Api::V1::Admin::AccountsController, type: :controller do
it 'removes user' do
expect(User.where(id: account.user.id).count).to eq 0
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
describe 'POST #enable' do

@ -22,9 +22,11 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
describe 'POST #create' do
let(:scopes) { 'write:filters' }
let(:irreversible) { true }
let(:whole_word) { false }
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
it 'returns http success' do
@ -34,11 +36,29 @@ RSpec.describe Api::V1::FiltersController, type: :controller do
it 'creates a filter' do
filter = user.account.custom_filters.first
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.irreversible?).to be true
expect(filter.irreversible?).to be irreversible
expect(filter.expires_at).to be_nil
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
describe 'GET #show' do

@ -22,7 +22,7 @@ describe DeliveryFailureTracker do
describe '#track_failure!' do
it 'marks URL as unavailable after 7 days of being called' do
6.times { |i| redis.sadd('exhausted_deliveries:example.com', i) }
6.times { |i| redis.sadd?('exhausted_deliveries:example.com', i) }
subject.track_failure!
expect(subject.days).to eq 7

@ -11,7 +11,7 @@ RSpec.describe Vacuum::FeedsVacuum do
redis.zadd(feed_key_for(inactive_user), 1, 1)
redis.zadd(feed_key_for(active_user), 1, 1)
redis.zadd(feed_key_for(inactive_user, 'reblogs'), 2, 2)
redis.sadd(feed_key_for(inactive_user, 'reblogs:2'), 3)
redis.sadd?(feed_key_for(inactive_user, 'reblogs:2'), 3)
subject.perform
end

@ -1,5 +1,48 @@
require 'rails_helper'
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

Loading…
Cancel
Save