Merge pull request #1513 from ClearlyClaire/glitch-soc/merge-upstream
Merge upstream changes
This commit is contained in:
		
						commit
						c7f04961b6
					
				
							
								
								
									
										5
									
								
								Gemfile
								
								
								
								
							
							
						
						
									
										5
									
								
								Gemfile
								
								
								
								
							| 
						 | 
					@ -111,7 +111,7 @@ group :development, :test do
 | 
				
			||||||
  gem 'i18n-tasks', '~> 0.9', require: false
 | 
					  gem 'i18n-tasks', '~> 0.9', require: false
 | 
				
			||||||
  gem 'pry-byebug', '~> 3.9'
 | 
					  gem 'pry-byebug', '~> 3.9'
 | 
				
			||||||
  gem 'pry-rails', '~> 0.3'
 | 
					  gem 'pry-rails', '~> 0.3'
 | 
				
			||||||
  gem 'rspec-rails', '~> 4.1'
 | 
					  gem 'rspec-rails', '~> 5.0'
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group :production, :test do
 | 
					group :production, :test do
 | 
				
			||||||
| 
						 | 
					@ -143,7 +143,7 @@ group :development do
 | 
				
			||||||
  gem 'rubocop', '~> 1.11', require: false
 | 
					  gem 'rubocop', '~> 1.11', require: false
 | 
				
			||||||
  gem 'rubocop-rails', '~> 2.9', require: false
 | 
					  gem 'rubocop-rails', '~> 2.9', require: false
 | 
				
			||||||
  gem 'brakeman', '~> 4.10', require: false
 | 
					  gem 'brakeman', '~> 4.10', require: false
 | 
				
			||||||
  gem 'bundler-audit', '~> 0.7', require: false
 | 
					  gem 'bundler-audit', '~> 0.8', require: false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  gem 'capistrano', '~> 3.16'
 | 
					  gem 'capistrano', '~> 3.16'
 | 
				
			||||||
  gem 'capistrano-rails', '~> 1.6'
 | 
					  gem 'capistrano-rails', '~> 1.6'
 | 
				
			||||||
| 
						 | 
					@ -155,7 +155,6 @@ end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
group :production do
 | 
					group :production do
 | 
				
			||||||
  gem 'lograge', '~> 0.11'
 | 
					  gem 'lograge', '~> 0.11'
 | 
				
			||||||
  gem 'redis-rails', '~> 5.0'
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'concurrent-ruby', require: false
 | 
					gem 'concurrent-ruby', require: false
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										33
									
								
								Gemfile.lock
								
								
								
								
							
							
						
						
									
										33
									
								
								Gemfile.lock
								
								
								
								
							| 
						 | 
					@ -115,9 +115,9 @@ GEM
 | 
				
			||||||
    bullet (6.1.4)
 | 
					    bullet (6.1.4)
 | 
				
			||||||
      activesupport (>= 3.0.0)
 | 
					      activesupport (>= 3.0.0)
 | 
				
			||||||
      uniform_notifier (~> 1.11)
 | 
					      uniform_notifier (~> 1.11)
 | 
				
			||||||
    bundler-audit (0.7.0.1)
 | 
					    bundler-audit (0.8.0)
 | 
				
			||||||
      bundler (>= 1.2.0, < 3)
 | 
					      bundler (>= 1.2.0, < 3)
 | 
				
			||||||
      thor (>= 0.18, < 2)
 | 
					      thor (~> 1.0)
 | 
				
			||||||
    byebug (11.1.3)
 | 
					    byebug (11.1.3)
 | 
				
			||||||
    capistrano (3.16.0)
 | 
					    capistrano (3.16.0)
 | 
				
			||||||
      airbrussh (>= 1.0.0)
 | 
					      airbrussh (>= 1.0.0)
 | 
				
			||||||
| 
						 | 
					@ -492,24 +492,8 @@ GEM
 | 
				
			||||||
      rdf (~> 3.1)
 | 
					      rdf (~> 3.1)
 | 
				
			||||||
    redcarpet (3.5.1)
 | 
					    redcarpet (3.5.1)
 | 
				
			||||||
    redis (4.2.5)
 | 
					    redis (4.2.5)
 | 
				
			||||||
    redis-actionpack (5.2.0)
 | 
					 | 
				
			||||||
      actionpack (>= 5, < 7)
 | 
					 | 
				
			||||||
      redis-rack (>= 2.1.0, < 3)
 | 
					 | 
				
			||||||
      redis-store (>= 1.1.0, < 2)
 | 
					 | 
				
			||||||
    redis-activesupport (5.2.0)
 | 
					 | 
				
			||||||
      activesupport (>= 3, < 7)
 | 
					 | 
				
			||||||
      redis-store (>= 1.3, < 2)
 | 
					 | 
				
			||||||
    redis-namespace (1.8.1)
 | 
					    redis-namespace (1.8.1)
 | 
				
			||||||
      redis (>= 3.0.4)
 | 
					      redis (>= 3.0.4)
 | 
				
			||||||
    redis-rack (2.1.3)
 | 
					 | 
				
			||||||
      rack (>= 2.0.8, < 3)
 | 
					 | 
				
			||||||
      redis-store (>= 1.2, < 2)
 | 
					 | 
				
			||||||
    redis-rails (5.0.2)
 | 
					 | 
				
			||||||
      redis-actionpack (>= 5.0, < 6)
 | 
					 | 
				
			||||||
      redis-activesupport (>= 5.0, < 6)
 | 
					 | 
				
			||||||
      redis-store (>= 1.2, < 2)
 | 
					 | 
				
			||||||
    redis-store (1.9.0)
 | 
					 | 
				
			||||||
      redis (>= 4, < 5)
 | 
					 | 
				
			||||||
    regexp_parser (2.1.1)
 | 
					    regexp_parser (2.1.1)
 | 
				
			||||||
    request_store (1.5.0)
 | 
					    request_store (1.5.0)
 | 
				
			||||||
      rack (>= 1.4)
 | 
					      rack (>= 1.4)
 | 
				
			||||||
| 
						 | 
					@ -531,10 +515,10 @@ GEM
 | 
				
			||||||
    rspec-mocks (3.10.2)
 | 
					    rspec-mocks (3.10.2)
 | 
				
			||||||
      diff-lcs (>= 1.2.0, < 2.0)
 | 
					      diff-lcs (>= 1.2.0, < 2.0)
 | 
				
			||||||
      rspec-support (~> 3.10.0)
 | 
					      rspec-support (~> 3.10.0)
 | 
				
			||||||
    rspec-rails (4.1.0)
 | 
					    rspec-rails (5.0.0)
 | 
				
			||||||
      actionpack (>= 4.2)
 | 
					      actionpack (>= 5.2)
 | 
				
			||||||
      activesupport (>= 4.2)
 | 
					      activesupport (>= 5.2)
 | 
				
			||||||
      railties (>= 4.2)
 | 
					      railties (>= 5.2)
 | 
				
			||||||
      rspec-core (~> 3.10)
 | 
					      rspec-core (~> 3.10)
 | 
				
			||||||
      rspec-expectations (~> 3.10)
 | 
					      rspec-expectations (~> 3.10)
 | 
				
			||||||
      rspec-mocks (~> 3.10)
 | 
					      rspec-mocks (~> 3.10)
 | 
				
			||||||
| 
						 | 
					@ -706,7 +690,7 @@ DEPENDENCIES
 | 
				
			||||||
  brakeman (~> 4.10)
 | 
					  brakeman (~> 4.10)
 | 
				
			||||||
  browser
 | 
					  browser
 | 
				
			||||||
  bullet (~> 6.1)
 | 
					  bullet (~> 6.1)
 | 
				
			||||||
  bundler-audit (~> 0.7)
 | 
					  bundler-audit (~> 0.8)
 | 
				
			||||||
  capistrano (~> 3.16)
 | 
					  capistrano (~> 3.16)
 | 
				
			||||||
  capistrano-rails (~> 1.6)
 | 
					  capistrano-rails (~> 1.6)
 | 
				
			||||||
  capistrano-rbenv (~> 2.2)
 | 
					  capistrano-rbenv (~> 2.2)
 | 
				
			||||||
| 
						 | 
					@ -792,9 +776,8 @@ DEPENDENCIES
 | 
				
			||||||
  redcarpet (~> 3.5)
 | 
					  redcarpet (~> 3.5)
 | 
				
			||||||
  redis (~> 4.2)
 | 
					  redis (~> 4.2)
 | 
				
			||||||
  redis-namespace (~> 1.8)
 | 
					  redis-namespace (~> 1.8)
 | 
				
			||||||
  redis-rails (~> 5.0)
 | 
					 | 
				
			||||||
  rqrcode (~> 1.2)
 | 
					  rqrcode (~> 1.2)
 | 
				
			||||||
  rspec-rails (~> 4.1)
 | 
					  rspec-rails (~> 5.0)
 | 
				
			||||||
  rspec-sidekiq (~> 3.1)
 | 
					  rspec-sidekiq (~> 3.1)
 | 
				
			||||||
  rspec_junit_formatter (~> 0.4)
 | 
					  rspec_junit_formatter (~> 0.4)
 | 
				
			||||||
  rubocop (~> 1.11)
 | 
					  rubocop (~> 1.11)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -22,7 +22,7 @@ module Admin
 | 
				
			||||||
      if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
 | 
					      if existing_domain_block.present? && !@domain_block.stricter_than?(existing_domain_block)
 | 
				
			||||||
        @domain_block.save
 | 
					        @domain_block.save
 | 
				
			||||||
        flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
 | 
					        flash.now[:alert] = I18n.t('admin.domain_blocks.existing_domain_block_html', name: existing_domain_block.domain, unblock_url: admin_domain_block_path(existing_domain_block)).html_safe # rubocop:disable Rails/OutputSafety
 | 
				
			||||||
        @domain_block.errors[:domain].clear
 | 
					        @domain_block.errors.delete(:domain)
 | 
				
			||||||
        render :new
 | 
					        render :new
 | 
				
			||||||
      else
 | 
					      else
 | 
				
			||||||
        if existing_domain_block.present?
 | 
					        if existing_domain_block.present?
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -32,6 +32,8 @@ module CacheConcern
 | 
				
			||||||
    return raw unless klass.respond_to?(:with_includes)
 | 
					    return raw unless klass.respond_to?(:with_includes)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
 | 
					    raw = raw.cache_ids.to_a if raw.is_a?(ActiveRecord::Relation)
 | 
				
			||||||
 | 
					    return [] if raw.empty?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
 | 
					    cached_keys_with_value = Rails.cache.read_multi(*raw).transform_keys(&:id)
 | 
				
			||||||
    uncached_ids           = raw.map(&:id) - cached_keys_with_value.keys
 | 
					    uncached_ids           = raw.map(&:id) - cached_keys_with_value.keys
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
import { Iterable, fromJS } from 'immutable';
 | 
					import { Iterable, fromJS } from 'immutable';
 | 
				
			||||||
import { hydrateCompose } from './compose';
 | 
					import { hydrateCompose } from './compose';
 | 
				
			||||||
import { importFetchedAccounts } from './importer';
 | 
					import { importFetchedAccounts } from './importer';
 | 
				
			||||||
 | 
					import { saveSettings } from './settings';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const STORE_HYDRATE = 'STORE_HYDRATE';
 | 
					export const STORE_HYDRATE = 'STORE_HYDRATE';
 | 
				
			||||||
export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
 | 
					export const STORE_HYDRATE_LAZY = 'STORE_HYDRATE_LAZY';
 | 
				
			||||||
| 
						 | 
					@ -9,9 +10,22 @@ const convertState = rawState =>
 | 
				
			||||||
  fromJS(rawState, (k, v) =>
 | 
					  fromJS(rawState, (k, v) =>
 | 
				
			||||||
    Iterable.isIndexed(v) ? v.toList() : v.toMap());
 | 
					    Iterable.isIndexed(v) ? v.toList() : v.toMap());
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					const applyMigrations = (state) => {
 | 
				
			||||||
 | 
					  return state.withMutations(state => {
 | 
				
			||||||
 | 
					    // Migrate glitch-soc local-only “Show unread marker” setting to Mastodon's setting
 | 
				
			||||||
 | 
					    if (state.getIn(['local_settings', 'notifications', 'show_unread']) !== undefined) {
 | 
				
			||||||
 | 
					      // Only change if the Mastodon setting does not deviate from default
 | 
				
			||||||
 | 
					      if (state.getIn(['settings', 'notifications', 'showUnread']) !== false) {
 | 
				
			||||||
 | 
					        state.setIn(['settings', 'notifications', 'showUnread'], state.getIn(['local_settings', 'notifications', 'show_unread']));
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					      state.removeIn(['local_settings', 'notifications', 'show_unread'])
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  });
 | 
				
			||||||
 | 
					};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export function hydrateStore(rawState) {
 | 
					export function hydrateStore(rawState) {
 | 
				
			||||||
  return dispatch => {
 | 
					  return dispatch => {
 | 
				
			||||||
    const state = convertState(rawState);
 | 
					    const state = applyMigrations(convertState(rawState));
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatch({
 | 
					    dispatch({
 | 
				
			||||||
      type: STORE_HYDRATE,
 | 
					      type: STORE_HYDRATE,
 | 
				
			||||||
| 
						 | 
					@ -20,5 +34,6 @@ export function hydrateStore(rawState) {
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dispatch(hydrateCompose());
 | 
					    dispatch(hydrateCompose());
 | 
				
			||||||
    dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
 | 
					    dispatch(importFetchedAccounts(Object.values(rawState.accounts)));
 | 
				
			||||||
 | 
					    dispatch(saveSettings());
 | 
				
			||||||
  };
 | 
					  };
 | 
				
			||||||
};
 | 
					};
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -113,14 +113,6 @@ class LocalSettingsPage extends React.PureComponent {
 | 
				
			||||||
            <FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Unread notifications favicon badge' />
 | 
					            <FormattedMessage id='settings.notifications.favicon_badge' defaultMessage='Unread notifications favicon badge' />
 | 
				
			||||||
            <span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage="Add a badge for unread notifications to the favicon" /></span>
 | 
					            <span className='hint'><FormattedMessage id='settings.notifications.favicon_badge.hint' defaultMessage="Add a badge for unread notifications to the favicon" /></span>
 | 
				
			||||||
          </LocalSettingsPageItem>
 | 
					          </LocalSettingsPageItem>
 | 
				
			||||||
          <LocalSettingsPageItem
 | 
					 | 
				
			||||||
            settings={settings}
 | 
					 | 
				
			||||||
            item={['notifications', 'show_unread']}
 | 
					 | 
				
			||||||
            id='mastodon-settings--notifications-show_unread'
 | 
					 | 
				
			||||||
            onChange={onChange}
 | 
					 | 
				
			||||||
          >
 | 
					 | 
				
			||||||
            <FormattedMessage id='settings.notifications.show_unread' defaultMessage='Show unread marker' />
 | 
					 | 
				
			||||||
          </LocalSettingsPageItem>
 | 
					 | 
				
			||||||
        </section>
 | 
					        </section>
 | 
				
			||||||
        <section>
 | 
					        <section>
 | 
				
			||||||
          <h2><FormattedMessage id='settings.layout_opts' defaultMessage='Layout options' /></h2>
 | 
					          <h2><FormattedMessage id='settings.layout_opts' defaultMessage='Layout options' /></h2>
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -56,6 +56,16 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
          <ClearColumnButton onClick={onClear} />
 | 
					          <ClearColumnButton onClick={onClear} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div role='group' aria-labelledby='notifications-unread-markers'>
 | 
				
			||||||
 | 
					          <span id='notifications-unread-markers' className='column-settings__section'>
 | 
				
			||||||
 | 
					            <FormattedMessage id='notifications.column_settings.unread_markers.category' defaultMessage='Unread notification markers' />
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='column-settings__row'>
 | 
				
			||||||
 | 
					            <SettingToggle id='unread-notification-markers' prefix='notifications' settings={settings} settingPath={['showUnread']} onChange={onChange} label={filterShowStr} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div role='group' aria-labelledby='notifications-filter-bar'>
 | 
					        <div role='group' aria-labelledby='notifications-filter-bar'>
 | 
				
			||||||
          <span id='notifications-filter-bar' className='column-settings__section'>
 | 
					          <span id='notifications-filter-bar' className='column-settings__section'>
 | 
				
			||||||
            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
 | 
					            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -67,8 +67,8 @@ const mapStateToProps = state => ({
 | 
				
			||||||
  hasMore: state.getIn(['notifications', 'hasMore']),
 | 
					  hasMore: state.getIn(['notifications', 'hasMore']),
 | 
				
			||||||
  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
 | 
					  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
 | 
				
			||||||
  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 | 
					  notifCleaningActive: state.getIn(['notifications', 'cleaningMode']),
 | 
				
			||||||
  lastReadId: state.getIn(['local_settings', 'notifications', 'show_unread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
 | 
					  lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
 | 
				
			||||||
  canMarkAsRead: state.getIn(['local_settings', 'notifications', 'show_unread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
 | 
					  canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
 | 
				
			||||||
  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
 | 
					  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,7 +55,6 @@ const initialState = ImmutableMap({
 | 
				
			||||||
  notifications : ImmutableMap({
 | 
					  notifications : ImmutableMap({
 | 
				
			||||||
    favicon_badge : false,
 | 
					    favicon_badge : false,
 | 
				
			||||||
    tab_badge     : true,
 | 
					    tab_badge     : true,
 | 
				
			||||||
    show_unread   : true,
 | 
					 | 
				
			||||||
  }),
 | 
					  }),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,6 +49,7 @@ const initialState = ImmutableMap({
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dismissPermissionBanner: false,
 | 
					    dismissPermissionBanner: false,
 | 
				
			||||||
 | 
					    showUnread: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shows: ImmutableMap({
 | 
					    shows: ImmutableMap({
 | 
				
			||||||
      follow: true,
 | 
					      follow: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -55,6 +55,16 @@ export default class ColumnSettings extends React.PureComponent {
 | 
				
			||||||
          <ClearColumnButton onClick={onClear} />
 | 
					          <ClearColumnButton onClick={onClear} />
 | 
				
			||||||
        </div>
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        <div role='group' aria-labelledby='notifications-unread-markers'>
 | 
				
			||||||
 | 
					          <span id='notifications-unread-markers' className='column-settings__section'>
 | 
				
			||||||
 | 
					            <FormattedMessage id='notifications.column_settings.unread_markers.category' defaultMessage='Unread notification markers' />
 | 
				
			||||||
 | 
					          </span>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					          <div className='column-settings__row'>
 | 
				
			||||||
 | 
					            <SettingToggle id='unread-notification-markers' prefix='notifications' settings={settings} settingPath={['showUnread']} onChange={onChange} label={filterShowStr} />
 | 
				
			||||||
 | 
					          </div>
 | 
				
			||||||
 | 
					        </div>
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        <div role='group' aria-labelledby='notifications-filter-bar'>
 | 
					        <div role='group' aria-labelledby='notifications-filter-bar'>
 | 
				
			||||||
          <span id='notifications-filter-bar' className='column-settings__section'>
 | 
					          <span id='notifications-filter-bar' className='column-settings__section'>
 | 
				
			||||||
            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
 | 
					            <FormattedMessage id='notifications.column_settings.filter_bar.category' defaultMessage='Quick filter bar' />
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -60,8 +60,8 @@ const mapStateToProps = state => ({
 | 
				
			||||||
  isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
 | 
					  isUnread: state.getIn(['notifications', 'unread']) > 0 || state.getIn(['notifications', 'pendingItems']).size > 0,
 | 
				
			||||||
  hasMore: state.getIn(['notifications', 'hasMore']),
 | 
					  hasMore: state.getIn(['notifications', 'hasMore']),
 | 
				
			||||||
  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
 | 
					  numPending: state.getIn(['notifications', 'pendingItems'], ImmutableList()).size,
 | 
				
			||||||
  lastReadId: state.getIn(['notifications', 'readMarkerId']),
 | 
					  lastReadId: state.getIn(['settings', 'notifications', 'showUnread']) ? state.getIn(['notifications', 'readMarkerId']) : '0',
 | 
				
			||||||
  canMarkAsRead: state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
 | 
					  canMarkAsRead: state.getIn(['settings', 'notifications', 'showUnread']) && state.getIn(['notifications', 'readMarkerId']) !== '0' && getNotifications(state).some(item => item !== null && compareId(item.get('id'), state.getIn(['notifications', 'readMarkerId'])) > 0),
 | 
				
			||||||
  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
 | 
					  needsNotificationPermission: state.getIn(['settings', 'notifications', 'alerts']).includes(true) && state.getIn(['notifications', 'browserSupport']) && state.getIn(['notifications', 'browserPermission']) === 'default' && !state.getIn(['settings', 'notifications', 'dismissPermissionBanner']),
 | 
				
			||||||
});
 | 
					});
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -45,6 +45,7 @@ const initialState = ImmutableMap({
 | 
				
			||||||
    }),
 | 
					    }),
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    dismissPermissionBanner: false,
 | 
					    dismissPermissionBanner: false,
 | 
				
			||||||
 | 
					    showUnread: true,
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    shows: ImmutableMap({
 | 
					    shows: ImmutableMap({
 | 
				
			||||||
      follow: true,
 | 
					      follow: true,
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,6 +17,8 @@ class EntityCache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def emoji(shortcodes, domain)
 | 
					  def emoji(shortcodes, domain)
 | 
				
			||||||
    shortcodes = Array(shortcodes)
 | 
					    shortcodes = Array(shortcodes)
 | 
				
			||||||
 | 
					    return [] if shortcodes.empty?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    cached       = Rails.cache.read_multi(*shortcodes.map { |shortcode| to_key(:emoji, shortcode, domain) })
 | 
					    cached       = Rails.cache.read_multi(*shortcodes.map { |shortcode| to_key(:emoji, shortcode, domain) })
 | 
				
			||||||
    uncached_ids = []
 | 
					    uncached_ids = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require 'singleton'
 | 
					require 'singleton'
 | 
				
			||||||
require_relative './sanitize_config'
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
class HTMLRenderer < Redcarpet::Render::HTML
 | 
					class HTMLRenderer < Redcarpet::Render::HTML
 | 
				
			||||||
  def block_code(code, language)
 | 
					  def block_code(code, language)
 | 
				
			||||||
| 
						 | 
					@ -223,9 +222,9 @@ class Formatter
 | 
				
			||||||
          original_url, static_url = emoji
 | 
					          original_url, static_url = emoji
 | 
				
			||||||
          replacement = begin
 | 
					          replacement = begin
 | 
				
			||||||
            if animate
 | 
					            if animate
 | 
				
			||||||
              "<img draggable=\"false\" class=\"emojione\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(original_url)}\" />"
 | 
					              image_tag(original_url, draggable: false, class: 'emojione', alt: ":#{shortcode}:", title: ":#{shortcode}:")
 | 
				
			||||||
            else
 | 
					            else
 | 
				
			||||||
              "<img draggable=\"false\" class=\"emojione custom-emoji\" alt=\":#{encode(shortcode)}:\" title=\":#{encode(shortcode)}:\" src=\"#{encode(static_url)}\" data-original=\"#{original_url}\" data-static=\"#{static_url}\" />"
 | 
					              image_tag(original_url, draggable: false, class: 'emojione custom-emoji', alt: ":#{shortcode}:", title: ":#{shortcode}:", data: { original: original_url, static: static_url })
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
          before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
 | 
					          before_html = shortname_start_index.positive? ? html[0..shortname_start_index - 1] : ''
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,7 +4,7 @@ class NotificationMailer < ApplicationMailer
 | 
				
			||||||
  helper :accounts
 | 
					  helper :accounts
 | 
				
			||||||
  helper :statuses
 | 
					  helper :statuses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  add_template_helper RoutingHelper
 | 
					  helper RoutingHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def mention(recipient, notification)
 | 
					  def mention(recipient, notification)
 | 
				
			||||||
    @me     = recipient
 | 
					    @me     = recipient
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ class UserMailer < Devise::Mailer
 | 
				
			||||||
  helper :instance
 | 
					  helper :instance
 | 
				
			||||||
  helper :statuses
 | 
					  helper :statuses
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  add_template_helper RoutingHelper
 | 
					  helper RoutingHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def confirmation_instructions(user, token, **)
 | 
					  def confirmation_instructions(user, token, **)
 | 
				
			||||||
    @resource = user
 | 
					    @resource = user
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -18,46 +18,4 @@ class AccountStat < ApplicationRecord
 | 
				
			||||||
  belongs_to :account, inverse_of: :account_stat
 | 
					  belongs_to :account, inverse_of: :account_stat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  update_index('accounts#account', :account)
 | 
					  update_index('accounts#account', :account)
 | 
				
			||||||
 | 
					 | 
				
			||||||
  def increment_count!(key)
 | 
					 | 
				
			||||||
    update(attributes_for_increment(key))
 | 
					 | 
				
			||||||
  rescue ActiveRecord::StaleObjectError, ActiveRecord::RecordNotUnique
 | 
					 | 
				
			||||||
    begin
 | 
					 | 
				
			||||||
      reload_with_id
 | 
					 | 
				
			||||||
    rescue ActiveRecord::RecordNotFound
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    retry
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def decrement_count!(key)
 | 
					 | 
				
			||||||
    update(attributes_for_decrement(key))
 | 
					 | 
				
			||||||
  rescue ActiveRecord::StaleObjectError, ActiveRecord::RecordNotUnique
 | 
					 | 
				
			||||||
    begin
 | 
					 | 
				
			||||||
      reload_with_id
 | 
					 | 
				
			||||||
    rescue ActiveRecord::RecordNotFound
 | 
					 | 
				
			||||||
      return
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    retry
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  private
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def attributes_for_increment(key)
 | 
					 | 
				
			||||||
    attrs = { key => public_send(key) + 1 }
 | 
					 | 
				
			||||||
    attrs[:last_status_at] = Time.now.utc if key == :statuses_count
 | 
					 | 
				
			||||||
    attrs
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def attributes_for_decrement(key)
 | 
					 | 
				
			||||||
    attrs = { key => [public_send(key) - 1, 0].max }
 | 
					 | 
				
			||||||
    attrs
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  def reload_with_id
 | 
					 | 
				
			||||||
    self.id = self.class.find_by!(account: account).id if new_record?
 | 
					 | 
				
			||||||
    reload
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,6 +3,8 @@
 | 
				
			||||||
module AccountCounters
 | 
					module AccountCounters
 | 
				
			||||||
  extend ActiveSupport::Concern
 | 
					  extend ActiveSupport::Concern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  ALLOWED_COUNTER_KEYS = %i(statuses_count following_count followers_count).freeze
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  included do
 | 
					  included do
 | 
				
			||||||
    has_one :account_stat, inverse_of: :account
 | 
					    has_one :account_stat, inverse_of: :account
 | 
				
			||||||
    after_save :save_account_stat
 | 
					    after_save :save_account_stat
 | 
				
			||||||
| 
						 | 
					@ -14,11 +16,65 @@ module AccountCounters
 | 
				
			||||||
           :following_count=,
 | 
					           :following_count=,
 | 
				
			||||||
           :followers_count,
 | 
					           :followers_count,
 | 
				
			||||||
           :followers_count=,
 | 
					           :followers_count=,
 | 
				
			||||||
           :increment_count!,
 | 
					 | 
				
			||||||
           :decrement_count!,
 | 
					 | 
				
			||||||
           :last_status_at,
 | 
					           :last_status_at,
 | 
				
			||||||
           to: :account_stat
 | 
					           to: :account_stat
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # @param [Symbol] key
 | 
				
			||||||
 | 
					  def increment_count!(key)
 | 
				
			||||||
 | 
					    update_count!(key, 1)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # @param [Symbol] key
 | 
				
			||||||
 | 
					  def decrement_count!(key)
 | 
				
			||||||
 | 
					    update_count!(key, -1)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # @param [Symbol] key
 | 
				
			||||||
 | 
					  # @param [Integer] value
 | 
				
			||||||
 | 
					  def update_count!(key, value)
 | 
				
			||||||
 | 
					    raise ArgumentError, "Invalid key #{key}" unless ALLOWED_COUNTER_KEYS.include?(key)
 | 
				
			||||||
 | 
					    raise ArgumentError, 'Do not call update_count! on dirty objects' if association(:account_stat).loaded? && account_stat&.changed? && account_stat.changed_attribute_names_to_save == %w(id)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    value = value.to_i
 | 
				
			||||||
 | 
					    default_value = value.positive? ? value : 0
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # We do an upsert using manually written SQL, as Rails' upsert method does
 | 
				
			||||||
 | 
					    # not seem to support writing expressions in the UPDATE clause, but only
 | 
				
			||||||
 | 
					    # re-insert the provided values instead.
 | 
				
			||||||
 | 
					    # Even ARel seem to be missing proper handling of upserts.
 | 
				
			||||||
 | 
					    sql = if value.positive? && key == :statuses_count
 | 
				
			||||||
 | 
					            <<-SQL.squish
 | 
				
			||||||
 | 
					              INSERT INTO account_stats(account_id, #{key}, created_at, updated_at, last_status_at)
 | 
				
			||||||
 | 
					                VALUES (:account_id, :default_value, now(), now(), now())
 | 
				
			||||||
 | 
					              ON CONFLICT (account_id) DO UPDATE
 | 
				
			||||||
 | 
					              SET #{key} = account_stats.#{key} + :value,
 | 
				
			||||||
 | 
					                  last_status_at = now(),
 | 
				
			||||||
 | 
					                  lock_version = account_stats.lock_version + 1,
 | 
				
			||||||
 | 
					                  updated_at = now()
 | 
				
			||||||
 | 
					              RETURNING id;
 | 
				
			||||||
 | 
					            SQL
 | 
				
			||||||
 | 
					          else
 | 
				
			||||||
 | 
					            <<-SQL.squish
 | 
				
			||||||
 | 
					              INSERT INTO account_stats(account_id, #{key}, created_at, updated_at)
 | 
				
			||||||
 | 
					                VALUES (:account_id, :default_value, now(), now())
 | 
				
			||||||
 | 
					              ON CONFLICT (account_id) DO UPDATE
 | 
				
			||||||
 | 
					              SET #{key} = account_stats.#{key} + :value,
 | 
				
			||||||
 | 
					                  lock_version = account_stats.lock_version + 1,
 | 
				
			||||||
 | 
					                  updated_at = now()
 | 
				
			||||||
 | 
					              RETURNING id;
 | 
				
			||||||
 | 
					            SQL
 | 
				
			||||||
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    sql = AccountStat.sanitize_sql([sql, account_id: id, default_value: default_value, value: value])
 | 
				
			||||||
 | 
					    account_stat_id = AccountStat.connection.exec_query(sql)[0]['id']
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    # Reload account_stat if it was loaded, taking into account newly-created unsaved records
 | 
				
			||||||
 | 
					    if association(:account_stat).loaded?
 | 
				
			||||||
 | 
					      account_stat.id = account_stat_id if account_stat.new_record?
 | 
				
			||||||
 | 
					      account_stat.reload
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def account_stat
 | 
					  def account_stat
 | 
				
			||||||
    super || build_account_stat
 | 
					    super || build_account_stat
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -49,12 +49,12 @@ class Notification < ApplicationRecord
 | 
				
			||||||
  belongs_to :from_account, class_name: 'Account', optional: true
 | 
					  belongs_to :from_account, class_name: 'Account', optional: true
 | 
				
			||||||
  belongs_to :activity, polymorphic: true, optional: true
 | 
					  belongs_to :activity, polymorphic: true, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :mention,        foreign_type: 'Mention',       foreign_key: 'activity_id', optional: true
 | 
					  belongs_to :mention,        foreign_key: 'activity_id', optional: true
 | 
				
			||||||
  belongs_to :status,         foreign_type: 'Status',        foreign_key: 'activity_id', optional: true
 | 
					  belongs_to :status,         foreign_key: 'activity_id', optional: true
 | 
				
			||||||
  belongs_to :follow,         foreign_type: 'Follow',        foreign_key: 'activity_id', optional: true
 | 
					  belongs_to :follow,         foreign_key: 'activity_id', optional: true
 | 
				
			||||||
  belongs_to :follow_request, foreign_type: 'FollowRequest', foreign_key: 'activity_id', optional: true
 | 
					  belongs_to :follow_request, foreign_key: 'activity_id', optional: true
 | 
				
			||||||
  belongs_to :favourite,      foreign_type: 'Favourite',     foreign_key: 'activity_id', optional: true
 | 
					  belongs_to :favourite,      foreign_key: 'activity_id', optional: true
 | 
				
			||||||
  belongs_to :poll,           foreign_type: 'Poll',          foreign_key: 'activity_id', optional: true
 | 
					  belongs_to :poll,           foreign_key: 'activity_id', optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :type, inclusion: { in: TYPES }
 | 
					  validates :type, inclusion: { in: TYPES }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class UrlValidator < ActiveModel::EachValidator
 | 
					class URLValidator < ActiveModel::EachValidator
 | 
				
			||||||
  def validate_each(record, attribute, value)
 | 
					  def validate_each(record, attribute, value)
 | 
				
			||||||
    record.errors.add(attribute, I18n.t('applications.invalid_url')) unless compliant?(value)
 | 
					    record.errors.add(attribute, I18n.t('applications.invalid_url')) unless compliant?(value)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,8 +6,9 @@ require 'rails/all'
 | 
				
			||||||
# you've limited to :test, :development, or :production.
 | 
					# you've limited to :test, :development, or :production.
 | 
				
			||||||
Bundler.require(*Rails.groups)
 | 
					Bundler.require(*Rails.groups)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require_relative '../app/lib/exceptions'
 | 
					require_relative '../lib/exceptions'
 | 
				
			||||||
require_relative '../lib/enumerable'
 | 
					require_relative '../lib/enumerable'
 | 
				
			||||||
 | 
					require_relative '../lib/sanitize_ext/sanitize_config'
 | 
				
			||||||
require_relative '../lib/redis/namespace_extensions'
 | 
					require_relative '../lib/redis/namespace_extensions'
 | 
				
			||||||
require_relative '../lib/paperclip/url_generator_extensions'
 | 
					require_relative '../lib/paperclip/url_generator_extensions'
 | 
				
			||||||
require_relative '../lib/paperclip/attachment_extensions'
 | 
					require_relative '../lib/paperclip/attachment_extensions'
 | 
				
			||||||
| 
						 | 
					@ -27,6 +28,7 @@ require_relative '../lib/webpacker/manifest_extensions'
 | 
				
			||||||
require_relative '../lib/webpacker/helper_extensions'
 | 
					require_relative '../lib/webpacker/helper_extensions'
 | 
				
			||||||
require_relative '../lib/action_dispatch/cookie_jar_extensions'
 | 
					require_relative '../lib/action_dispatch/cookie_jar_extensions'
 | 
				
			||||||
require_relative '../lib/rails/engine_extensions'
 | 
					require_relative '../lib/rails/engine_extensions'
 | 
				
			||||||
 | 
					require_relative '../lib/active_record/database_tasks_extensions'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
Dotenv::Railtie.load
 | 
					Dotenv::Railtie.load
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ Rails.application.configure do
 | 
				
			||||||
  if Rails.root.join('tmp/caching-dev.txt').exist?
 | 
					  if Rails.root.join('tmp/caching-dev.txt').exist?
 | 
				
			||||||
    config.action_controller.perform_caching = true
 | 
					    config.action_controller.perform_caching = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    config.cache_store = :redis_store, ENV['REDIS_URL'], REDIS_CACHE_PARAMS
 | 
					    config.cache_store = :redis_cache_store, REDIS_CACHE_PARAMS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    config.public_file_server.headers = {
 | 
					    config.public_file_server.headers = {
 | 
				
			||||||
      'Cache-Control' => "public, max-age=#{2.days.to_i}",
 | 
					      'Cache-Control' => "public, max-age=#{2.days.to_i}",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -52,7 +52,7 @@ Rails.application.configure do
 | 
				
			||||||
  config.log_tags = [:request_id]
 | 
					  config.log_tags = [:request_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Use a different cache store in production.
 | 
					  # Use a different cache store in production.
 | 
				
			||||||
  config.cache_store = :redis_store, ENV['CACHE_REDIS_URL'], REDIS_CACHE_PARAMS
 | 
					  config.cache_store = :redis_cache_store, REDIS_CACHE_PARAMS
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Ignore bad email addresses and do not raise email delivery errors.
 | 
					  # Ignore bad email addresses and do not raise email delivery errors.
 | 
				
			||||||
  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
 | 
					  # Set this to true and configure the email server for immediate delivery to raise delivery errors.
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,10 @@ ActiveSupport::Inflector.inflections(:en) do |inflect|
 | 
				
			||||||
  inflect.acronym 'JsonLd'
 | 
					  inflect.acronym 'JsonLd'
 | 
				
			||||||
  inflect.acronym 'NodeInfo'
 | 
					  inflect.acronym 'NodeInfo'
 | 
				
			||||||
  inflect.acronym 'Ed25519'
 | 
					  inflect.acronym 'Ed25519'
 | 
				
			||||||
 | 
					  inflect.acronym 'TOC'
 | 
				
			||||||
 | 
					  inflect.acronym 'RSS'
 | 
				
			||||||
 | 
					  inflect.acronym 'REST'
 | 
				
			||||||
 | 
					  inflect.acronym 'URL'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  inflect.singular 'data', 'data'
 | 
					  inflect.singular 'data', 'data'
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
namespace    = ENV.fetch('REDIS_NAMESPACE') { nil }
 | 
					namespace    = ENV.fetch('REDIS_NAMESPACE') { nil }
 | 
				
			||||||
redis_params = { url: ENV['REDIS_URL'] }
 | 
					redis_params = { url: ENV['REDIS_URL'], driver: :hiredis }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
if namespace
 | 
					if namespace
 | 
				
			||||||
  redis_params[:namespace] = namespace
 | 
					  redis_params[:namespace] = namespace
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
class AddUrlToStatuses < ActiveRecord::Migration[4.2]
 | 
					class AddURLToStatuses < ActiveRecord::Migration[4.2]
 | 
				
			||||||
  def change
 | 
					  def change
 | 
				
			||||||
    add_column :statuses, :url, :string, null: true, default: nil
 | 
					    add_column :statuses, :url, :string, null: true, default: nil
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
class AddUrlToAccounts < ActiveRecord::Migration[4.2]
 | 
					class AddURLToAccounts < ActiveRecord::Migration[4.2]
 | 
				
			||||||
  def change
 | 
					  def change
 | 
				
			||||||
    add_column :accounts, :url, :string, null: true, default: nil
 | 
					    add_column :accounts, :url, :string, null: true, default: nil
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
class AddAvatarRemoteUrlToAccounts < ActiveRecord::Migration[4.2]
 | 
					class AddAvatarRemoteURLToAccounts < ActiveRecord::Migration[4.2]
 | 
				
			||||||
  def change
 | 
					  def change
 | 
				
			||||||
    add_column :accounts, :avatar_remote_url, :string, null: true, default: nil
 | 
					    add_column :accounts, :avatar_remote_url, :string, null: true, default: nil
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,12 +7,12 @@ end
 | 
				
			||||||
class RailsSettingsMigration < MIGRATION_BASE_CLASS
 | 
					class RailsSettingsMigration < MIGRATION_BASE_CLASS
 | 
				
			||||||
  def self.up
 | 
					  def self.up
 | 
				
			||||||
    create_table :settings do |t|
 | 
					    create_table :settings do |t|
 | 
				
			||||||
      t.string     :var, :null => false
 | 
					      t.string     :var, null: false
 | 
				
			||||||
      t.text       :value
 | 
					      t.text       :value
 | 
				
			||||||
      t.references :target, :null => false, :polymorphic => true
 | 
					      t.references :target, null: false, polymorphic: true, index: { name: 'index_settings_on_target_type_and_target_id' }
 | 
				
			||||||
      t.timestamps :null => true
 | 
					      t.timestamps null: true
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    add_index :settings, [ :target_type, :target_id, :var ], :unique => true
 | 
					    add_index :settings, [ :target_type, :target_id, :var ], unique: true
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def self.down
 | 
					  def self.down
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
class AddHeaderRemoteUrlToAccounts < ActiveRecord::Migration[5.0]
 | 
					class AddHeaderRemoteURLToAccounts < ActiveRecord::Migration[5.0]
 | 
				
			||||||
  def change
 | 
					  def change
 | 
				
			||||||
    add_column :accounts, :header_remote_url, :string, null: false, default: ''
 | 
					    add_column :accounts, :header_remote_url, :string, null: false, default: ''
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,7 @@
 | 
				
			||||||
class StatusIdsToTimestampIds < ActiveRecord::Migration[5.1]
 | 
					class StatusIdsToTimestampIds < ActiveRecord::Migration[5.1]
 | 
				
			||||||
  def up
 | 
					  def up
 | 
				
			||||||
    # Prepare the function we will use to generate IDs.
 | 
					    # Prepare the function we will use to generate IDs.
 | 
				
			||||||
    Rake::Task['db:define_timestamp_id'].execute
 | 
					    Mastodon::Snowflake.define_timestamp_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Set up the statuses.id column to use our timestamp-based IDs.
 | 
					    # Set up the statuses.id column to use our timestamp-based IDs.
 | 
				
			||||||
    ActiveRecord::Base.connection.execute(<<~SQL)
 | 
					    ActiveRecord::Base.connection.execute(<<~SQL)
 | 
				
			||||||
| 
						 | 
					@ -11,7 +11,7 @@ class StatusIdsToTimestampIds < ActiveRecord::Migration[5.1]
 | 
				
			||||||
    SQL
 | 
					    SQL
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Make sure we have a sequence to use.
 | 
					    # Make sure we have a sequence to use.
 | 
				
			||||||
    Rake::Task['db:ensure_id_sequences_exist'].execute
 | 
					    Mastodon::Snowflake.ensure_id_sequences_exist
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def down
 | 
					  def down
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3,7 +3,7 @@ class CreateAdminActionLogs < ActiveRecord::Migration[5.1]
 | 
				
			||||||
    create_table :admin_action_logs do |t|
 | 
					    create_table :admin_action_logs do |t|
 | 
				
			||||||
      t.belongs_to :account, foreign_key: { on_delete: :cascade }
 | 
					      t.belongs_to :account, foreign_key: { on_delete: :cascade }
 | 
				
			||||||
      t.string :action, null: false, default: ''
 | 
					      t.string :action, null: false, default: ''
 | 
				
			||||||
      t.references :target, polymorphic: true
 | 
					      t.references :target, polymorphic: true, index: { name: 'index_admin_action_logs_on_target_type_and_target_id' }
 | 
				
			||||||
      t.text :recorded_changes, null: false, default: ''
 | 
					      t.text :recorded_changes, null: false, default: ''
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      t.timestamps
 | 
					      t.timestamps
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,6 @@
 | 
				
			||||||
require Rails.root.join('lib', 'mastodon', 'migration_helpers')
 | 
					require Rails.root.join('lib', 'mastodon', 'migration_helpers')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class AddEmbedUrlToPreviewCards < ActiveRecord::Migration[5.1]
 | 
					class AddEmbedURLToPreviewCards < ActiveRecord::Migration[5.1]
 | 
				
			||||||
  include Mastodon::MigrationHelpers
 | 
					  include Mastodon::MigrationHelpers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  disable_ddl_transaction!
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
class AddFeaturedCollectionUrlToAccounts < ActiveRecord::Migration[5.1]
 | 
					class AddFeaturedCollectionURLToAccounts < ActiveRecord::Migration[5.1]
 | 
				
			||||||
  def change
 | 
					  def change
 | 
				
			||||||
    add_column :accounts, :featured_collection_url, :string
 | 
					    add_column :accounts, :featured_collection_url, :string
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -37,7 +37,7 @@ class FixAccountsUniqueIndex < ActiveRecord::Migration[5.2]
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    duplicates = Account.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM accounts GROUP BY lower(username), lower(domain) HAVING count(*) > 1').to_hash
 | 
					    duplicates = Account.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM accounts GROUP BY lower(username), lower(domain) HAVING count(*) > 1').to_ary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    duplicates.each do |row|
 | 
					    duplicates.each do |row|
 | 
				
			||||||
      deduplicate_account!(row['ids'].split(','))
 | 
					      deduplicate_account!(row['ids'].split(','))
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,8 +17,8 @@ class MigrateAccountConversations < ActiveRecord::Migration[5.2]
 | 
				
			||||||
    belongs_to :account, optional: true
 | 
					    belongs_to :account, optional: true
 | 
				
			||||||
    belongs_to :activity, polymorphic: true, optional: true
 | 
					    belongs_to :activity, polymorphic: true, optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    belongs_to :status,         foreign_type: 'Status',        foreign_key: 'activity_id', optional: true
 | 
					    belongs_to :status,  foreign_key: 'activity_id', optional: true
 | 
				
			||||||
    belongs_to :mention,        foreign_type: 'Mention',       foreign_key: 'activity_id', optional: true
 | 
					    belongs_to :mention, foreign_key: 'activity_id', optional: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def target_status
 | 
					    def target_status
 | 
				
			||||||
      mention&.status
 | 
					      mention&.status
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ class DowncaseCustomEmojiDomains < ActiveRecord::Migration[5.2]
 | 
				
			||||||
  disable_ddl_transaction!
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def up
 | 
					  def up
 | 
				
			||||||
    duplicates = CustomEmoji.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM custom_emojis GROUP BY shortcode, lower(domain) HAVING count(*) > 1').to_hash
 | 
					    duplicates = CustomEmoji.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM custom_emojis GROUP BY shortcode, lower(domain) HAVING count(*) > 1').to_ary
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    duplicates.each do |row|
 | 
					    duplicates.each do |row|
 | 
				
			||||||
      CustomEmoji.where(id: row['ids'].split(',')[0...-1]).destroy_all
 | 
					      CustomEmoji.where(id: row['ids'].split(',')[0...-1]).destroy_all
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ class AddCaseInsensitiveIndexToTags < ActiveRecord::Migration[5.2]
 | 
				
			||||||
  disable_ddl_transaction!
 | 
					  disable_ddl_transaction!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def up
 | 
					  def up
 | 
				
			||||||
    Tag.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM tags GROUP BY lower(name) HAVING count(*) > 1').to_hash.each do |row|
 | 
					    Tag.connection.select_all('SELECT string_agg(id::text, \',\') AS ids FROM tags GROUP BY lower(name) HAVING count(*) > 1').to_ary.each do |row|
 | 
				
			||||||
      canonical_tag_id  = row['ids'].split(',').first
 | 
					      canonical_tag_id  = row['ids'].split(',').first
 | 
				
			||||||
      redundant_tag_ids = row['ids'].split(',')[1..-1]
 | 
					      redundant_tag_ids = row['ids'].split(',')[1..-1]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,4 @@
 | 
				
			||||||
class AddDevicesUrlToAccounts < ActiveRecord::Migration[5.2]
 | 
					class AddDevicesURLToAccounts < ActiveRecord::Migration[5.2]
 | 
				
			||||||
  def change
 | 
					  def change
 | 
				
			||||||
    add_column :accounts, :devices_url, :string
 | 
					    add_column :accounts, :devices_url, :string
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,20 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require_relative '../mastodon/snowflake'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module ActiveRecord
 | 
				
			||||||
 | 
					  module Tasks
 | 
				
			||||||
 | 
					    module DatabaseTasks
 | 
				
			||||||
 | 
					      original_load_schema = instance_method(:load_schema)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      define_method(:load_schema) do |db_config, *args|
 | 
				
			||||||
 | 
					        ActiveRecord::Base.establish_connection(db_config)
 | 
				
			||||||
 | 
					        Mastodon::Snowflake.define_timestamp_id
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        original_load_schema.bind(self).call(db_config, *args)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        Mastodon::Snowflake.ensure_id_sequences_exist
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -14,7 +14,7 @@ module Mastodon
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    MIN_SUPPORTED_VERSION = 2019_10_01_213028
 | 
					    MIN_SUPPORTED_VERSION = 2019_10_01_213028
 | 
				
			||||||
    MAX_SUPPORTED_VERSION = 2020_12_18_054746
 | 
					    MAX_SUPPORTED_VERSION = 2021_03_08_133107
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Stubs to enjoy ActiveRecord queries while not depending on a particular
 | 
					    # Stubs to enjoy ActiveRecord queries while not depending on a particular
 | 
				
			||||||
    # version of the code/database
 | 
					    # version of the code/database
 | 
				
			||||||
| 
						 | 
					@ -142,7 +142,6 @@ module Mastodon
 | 
				
			||||||
      @prompt.warn 'Please make sure to stop Mastodon and have a backup.'
 | 
					      @prompt.warn 'Please make sure to stop Mastodon and have a backup.'
 | 
				
			||||||
      exit(1) unless @prompt.yes?('Continue?')
 | 
					      exit(1) unless @prompt.yes?('Continue?')
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      deduplicate_accounts!
 | 
					 | 
				
			||||||
      deduplicate_users!
 | 
					      deduplicate_users!
 | 
				
			||||||
      deduplicate_account_domain_blocks!
 | 
					      deduplicate_account_domain_blocks!
 | 
				
			||||||
      deduplicate_account_identity_proofs!
 | 
					      deduplicate_account_identity_proofs!
 | 
				
			||||||
| 
						 | 
					@ -157,6 +156,7 @@ module Mastodon
 | 
				
			||||||
      deduplicate_media_attachments!
 | 
					      deduplicate_media_attachments!
 | 
				
			||||||
      deduplicate_preview_cards!
 | 
					      deduplicate_preview_cards!
 | 
				
			||||||
      deduplicate_statuses!
 | 
					      deduplicate_statuses!
 | 
				
			||||||
 | 
					      deduplicate_accounts!
 | 
				
			||||||
      deduplicate_tags!
 | 
					      deduplicate_tags!
 | 
				
			||||||
      deduplicate_webauthn_credentials!
 | 
					      deduplicate_webauthn_credentials!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -41,42 +41,18 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
module Mastodon
 | 
					module Mastodon
 | 
				
			||||||
  module MigrationHelpers
 | 
					  module MigrationHelpers
 | 
				
			||||||
    # Stub for Database.postgresql? from GitLab
 | 
					 | 
				
			||||||
    def self.postgresql?
 | 
					 | 
				
			||||||
      ActiveRecord::Base.configurations[Rails.env]['adapter'].casecmp('postgresql').zero?
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Stub for Database.mysql? from GitLab
 | 
					 | 
				
			||||||
    def self.mysql?
 | 
					 | 
				
			||||||
      ActiveRecord::Base.configurations[Rails.env]['adapter'].casecmp('mysql2').zero?
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    # Model that can be used for querying permissions of a SQL user.
 | 
					    # Model that can be used for querying permissions of a SQL user.
 | 
				
			||||||
    class Grant < ActiveRecord::Base
 | 
					    class Grant < ActiveRecord::Base
 | 
				
			||||||
      self.table_name =
 | 
					      self.table_name = 'information_schema.role_table_grants'
 | 
				
			||||||
        if Mastodon::MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
          'information_schema.role_table_grants'
 | 
					 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
          'mysql.user'
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      def self.scope_to_current_user
 | 
					      def self.scope_to_current_user
 | 
				
			||||||
        if Mastodon::MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
        where('grantee = user')
 | 
					        where('grantee = user')
 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
          where("CONCAT(User, '@', Host) = current_user()")
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      # Returns true if the current user can create and execute triggers on the
 | 
					      # Returns true if the current user can create and execute triggers on the
 | 
				
			||||||
      # given table.
 | 
					      # given table.
 | 
				
			||||||
      def self.create_and_execute_trigger?(table)
 | 
					      def self.create_and_execute_trigger?(table)
 | 
				
			||||||
        priv =
 | 
					        priv = where(privilege_type: 'TRIGGER', table_name: table)
 | 
				
			||||||
          if Mastodon::MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
            where(privilege_type: 'TRIGGER', table_name: table)
 | 
					 | 
				
			||||||
          else
 | 
					 | 
				
			||||||
            where(Trigger_priv: 'Y')
 | 
					 | 
				
			||||||
          end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        priv.scope_to_current_user.any?
 | 
					        priv.scope_to_current_user.any?
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					@ -141,10 +117,8 @@ module Mastodon
 | 
				
			||||||
          'in the body of your migration class'
 | 
					          'in the body of your migration class'
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
      options = options.merge({ algorithm: :concurrently })
 | 
					      options = options.merge({ algorithm: :concurrently })
 | 
				
			||||||
      disable_statement_timeout
 | 
					      disable_statement_timeout
 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      add_index(table_name, column_name, options)
 | 
					      add_index(table_name, column_name, options)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -199,8 +173,6 @@ module Mastodon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Only available on Postgresql >= 9.2
 | 
					    # Only available on Postgresql >= 9.2
 | 
				
			||||||
    def supports_drop_index_concurrently?
 | 
					    def supports_drop_index_concurrently?
 | 
				
			||||||
      return false unless MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
 | 
					      version = select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      version >= 90200
 | 
					      version >= 90200
 | 
				
			||||||
| 
						 | 
					@ -226,13 +198,7 @@ module Mastodon
 | 
				
			||||||
      # While MySQL does allow disabling of foreign keys it has no equivalent
 | 
					      # While MySQL does allow disabling of foreign keys it has no equivalent
 | 
				
			||||||
      # of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
 | 
					      # of PostgreSQL's "VALIDATE CONSTRAINT". As a result we'll just fall
 | 
				
			||||||
      # back to the normal foreign key procedure.
 | 
					      # back to the normal foreign key procedure.
 | 
				
			||||||
      if MigrationHelpers.mysql?
 | 
					 | 
				
			||||||
        return add_foreign_key(source, target,
 | 
					 | 
				
			||||||
                               column: column,
 | 
					 | 
				
			||||||
                               on_delete: on_delete)
 | 
					 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
      on_delete = 'SET NULL' if on_delete == :nullify
 | 
					      on_delete = 'SET NULL' if on_delete == :nullify
 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      disable_statement_timeout
 | 
					      disable_statement_timeout
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -270,7 +236,7 @@ module Mastodon
 | 
				
			||||||
    # the database. Disable the session's statement timeout to ensure
 | 
					    # the database. Disable the session's statement timeout to ensure
 | 
				
			||||||
    # migrations don't get killed prematurely. (PostgreSQL only)
 | 
					    # migrations don't get killed prematurely. (PostgreSQL only)
 | 
				
			||||||
    def disable_statement_timeout
 | 
					    def disable_statement_timeout
 | 
				
			||||||
      execute('SET statement_timeout TO 0') if MigrationHelpers.postgresql?
 | 
					      execute('SET statement_timeout TO 0')
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Updates the value of a column in batches.
 | 
					    # Updates the value of a column in batches.
 | 
				
			||||||
| 
						 | 
					@ -319,7 +285,7 @@ module Mastodon
 | 
				
			||||||
        count_arel = table.project(Arel.star.count.as('count'))
 | 
					        count_arel = table.project(Arel.star.count.as('count'))
 | 
				
			||||||
        count_arel = yield table, count_arel if block_given?
 | 
					        count_arel = yield table, count_arel if block_given?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        total = exec_query(count_arel.to_sql).to_hash.first['count'].to_i
 | 
					        total = exec_query(count_arel.to_sql).to_ary.first['count'].to_i
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        return if total == 0
 | 
					        return if total == 0
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					@ -335,7 +301,7 @@ module Mastodon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      start_arel = table.project(table[:id]).order(table[:id].asc).take(1)
 | 
					      start_arel = table.project(table[:id]).order(table[:id].asc).take(1)
 | 
				
			||||||
      start_arel = yield table, start_arel if block_given?
 | 
					      start_arel = yield table, start_arel if block_given?
 | 
				
			||||||
      first_row = exec_query(start_arel.to_sql).to_hash.first
 | 
					      first_row = exec_query(start_arel.to_sql).to_ary.first
 | 
				
			||||||
      # In case there are no rows but we didn't catch it in the estimated size:
 | 
					      # In case there are no rows but we didn't catch it in the estimated size:
 | 
				
			||||||
      return unless first_row
 | 
					      return unless first_row
 | 
				
			||||||
      start_id = first_row['id'].to_i
 | 
					      start_id = first_row['id'].to_i
 | 
				
			||||||
| 
						 | 
					@ -356,7 +322,7 @@ module Mastodon
 | 
				
			||||||
            .skip(batch_size)
 | 
					            .skip(batch_size)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          stop_arel = yield table, stop_arel if block_given?
 | 
					          stop_arel = yield table, stop_arel if block_given?
 | 
				
			||||||
          stop_row = exec_query(stop_arel.to_sql).to_hash.first
 | 
					          stop_row = exec_query(stop_arel.to_sql).to_ary.first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          update_arel = Arel::UpdateManager.new
 | 
					          update_arel = Arel::UpdateManager.new
 | 
				
			||||||
            .table(table)
 | 
					            .table(table)
 | 
				
			||||||
| 
						 | 
					@ -487,11 +453,7 @@ module Mastodon
 | 
				
			||||||
      # If we were in the middle of update_column_in_batches, we should remove
 | 
					      # If we were in the middle of update_column_in_batches, we should remove
 | 
				
			||||||
      # the old column and start over, as we have no idea where we were.
 | 
					      # the old column and start over, as we have no idea where we were.
 | 
				
			||||||
      if column_for(table, new)
 | 
					      if column_for(table, new)
 | 
				
			||||||
        if MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
        remove_rename_triggers_for_postgresql(table, trigger_name)
 | 
					        remove_rename_triggers_for_postgresql(table, trigger_name)
 | 
				
			||||||
        else
 | 
					 | 
				
			||||||
          remove_rename_triggers_for_mysql(trigger_name)
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
        remove_column(table, new)
 | 
					        remove_column(table, new)
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
| 
						 | 
					@ -521,13 +483,8 @@ module Mastodon
 | 
				
			||||||
      quoted_old = quote_column_name(old)
 | 
					      quoted_old = quote_column_name(old)
 | 
				
			||||||
      quoted_new = quote_column_name(new)
 | 
					      quoted_new = quote_column_name(new)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
      install_rename_triggers_for_postgresql(trigger_name, quoted_table,
 | 
					      install_rename_triggers_for_postgresql(trigger_name, quoted_table,
 | 
				
			||||||
                                             quoted_old, quoted_new)
 | 
					                                             quoted_old, quoted_new)
 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        install_rename_triggers_for_mysql(trigger_name, quoted_table,
 | 
					 | 
				
			||||||
                                          quoted_old, quoted_new)
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      update_column_in_batches(table, new, Arel::Table.new(table)[old])
 | 
					      update_column_in_batches(table, new, Arel::Table.new(table)[old])
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -685,11 +642,7 @@ module Mastodon
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      check_trigger_permissions!(table)
 | 
					      check_trigger_permissions!(table)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if MigrationHelpers.postgresql?
 | 
					 | 
				
			||||||
      remove_rename_triggers_for_postgresql(table, trigger_name)
 | 
					      remove_rename_triggers_for_postgresql(table, trigger_name)
 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
        remove_rename_triggers_for_mysql(trigger_name)
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      remove_column(table, old)
 | 
					      remove_column(table, old)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -844,19 +797,10 @@ module Mastodon
 | 
				
			||||||
      quoted_pattern = Arel::Nodes::Quoted.new(pattern.to_s)
 | 
					      quoted_pattern = Arel::Nodes::Quoted.new(pattern.to_s)
 | 
				
			||||||
      quoted_replacement = Arel::Nodes::Quoted.new(replacement.to_s)
 | 
					      quoted_replacement = Arel::Nodes::Quoted.new(replacement.to_s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      if MigrationHelpers.mysql?
 | 
					 | 
				
			||||||
        locate = Arel::Nodes::NamedFunction
 | 
					 | 
				
			||||||
          .new('locate', [quoted_pattern, column])
 | 
					 | 
				
			||||||
        insert_in_place = Arel::Nodes::NamedFunction
 | 
					 | 
				
			||||||
          .new('insert', [column, locate, pattern.size, quoted_replacement])
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
        Arel::Nodes::SqlLiteral.new(insert_in_place.to_sql)
 | 
					 | 
				
			||||||
      else
 | 
					 | 
				
			||||||
      replace = Arel::Nodes::NamedFunction
 | 
					      replace = Arel::Nodes::NamedFunction
 | 
				
			||||||
        .new("regexp_replace", [column, quoted_pattern, quoted_replacement])
 | 
					        .new("regexp_replace", [column, quoted_pattern, quoted_replacement])
 | 
				
			||||||
      Arel::Nodes::SqlLiteral.new(replace.to_sql)
 | 
					      Arel::Nodes::SqlLiteral.new(replace.to_sql)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def remove_foreign_key_without_error(*args)
 | 
					    def remove_foreign_key_without_error(*args)
 | 
				
			||||||
      remove_foreign_key(*args)
 | 
					      remove_foreign_key(*args)
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -27,6 +27,8 @@ namespace       = ENV.fetch('REDIS_NAMESPACE', nil)
 | 
				
			||||||
cache_namespace = namespace ? namespace + '_cache' : 'cache'
 | 
					cache_namespace = namespace ? namespace + '_cache' : 'cache'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
REDIS_CACHE_PARAMS = {
 | 
					REDIS_CACHE_PARAMS = {
 | 
				
			||||||
 | 
					  driver: :hiredis,
 | 
				
			||||||
 | 
					  url: ENV['REDIS_URL'],
 | 
				
			||||||
  expires_in: 10.minutes,
 | 
					  expires_in: 10.minutes,
 | 
				
			||||||
  namespace: cache_namespace,
 | 
					  namespace: cache_namespace,
 | 
				
			||||||
}.freeze
 | 
					}.freeze
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,36 +1,5 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require_relative '../mastodon/snowflake'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
def each_schema_load_environment
 | 
					 | 
				
			||||||
  # If we're in development, also run this for the test environment.
 | 
					 | 
				
			||||||
  # This is a somewhat hacky way to do this, so here's why:
 | 
					 | 
				
			||||||
  # 1. We have to define this before we load the schema, or we won't
 | 
					 | 
				
			||||||
  #    have a timestamp_id function when we get to it in the schema.
 | 
					 | 
				
			||||||
  # 2. db:setup calls db:schema:load_if_ruby, which calls
 | 
					 | 
				
			||||||
  #    db:schema:load, which we define above as having a prerequisite
 | 
					 | 
				
			||||||
  #    of this task.
 | 
					 | 
				
			||||||
  # 3. db:schema:load ends up running
 | 
					 | 
				
			||||||
  #    ActiveRecord::Tasks::DatabaseTasks.load_schema_current, which
 | 
					 | 
				
			||||||
  #    calls a private method `each_current_configuration`, which
 | 
					 | 
				
			||||||
  #    explicitly also does the loading for the `test` environment
 | 
					 | 
				
			||||||
  #    if the current environment is `development`, so we end up
 | 
					 | 
				
			||||||
  #    needing to do the same, and we can't even use the same method
 | 
					 | 
				
			||||||
  #    to do it.
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  if Rails.env.development?
 | 
					 | 
				
			||||||
    test_conf = ActiveRecord::Base.configurations['test']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    if test_conf['database']&.present?
 | 
					 | 
				
			||||||
      ActiveRecord::Base.establish_connection(:test)
 | 
					 | 
				
			||||||
      yield
 | 
					 | 
				
			||||||
      ActiveRecord::Base.establish_connection(Rails.env.to_sym)
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  yield
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
namespace :db do
 | 
					namespace :db do
 | 
				
			||||||
  namespace :migrate do
 | 
					  namespace :migrate do
 | 
				
			||||||
    desc 'Setup the db or migrate depending on state of db'
 | 
					    desc 'Setup the db or migrate depending on state of db'
 | 
				
			||||||
| 
						 | 
					@ -50,7 +19,7 @@ namespace :db do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  task :post_migration_hook do
 | 
					  task :post_migration_hook do
 | 
				
			||||||
    at_exit do
 | 
					    at_exit do
 | 
				
			||||||
      unless %w(C POSIX).include?(ActiveRecord::Base.connection.execute('SELECT datcollate FROM pg_database WHERE datname = current_database();').first['datcollate'])
 | 
					      unless %w(C POSIX).include?(ActiveRecord::Base.connection.select_one('SELECT datcollate FROM pg_database WHERE datname = current_database();')['datcollate'])
 | 
				
			||||||
        warn <<~WARNING
 | 
					        warn <<~WARNING
 | 
				
			||||||
          Your database collation is susceptible to index corruption.
 | 
					          Your database collation is susceptible to index corruption.
 | 
				
			||||||
            (This warning does not indicate that index corruption has occured and can be ignored)
 | 
					            (This warning does not indicate that index corruption has occured and can be ignored)
 | 
				
			||||||
| 
						 | 
					@ -60,30 +29,11 @@ namespace :db do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  task :pre_migration_check do
 | 
				
			||||||
 | 
					    version = ActiveRecord::Base.connection.select_one("SELECT current_setting('server_version_num') AS v")['v'].to_i
 | 
				
			||||||
 | 
					    abort 'ERROR: This version of Mastodon requires PostgreSQL 9.5 or newer. Please update PostgreSQL before updating Mastodon.' if version < 90_500
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  Rake::Task['db:migrate'].enhance(['db:pre_migration_check'])
 | 
				
			||||||
  Rake::Task['db:migrate'].enhance(['db:post_migration_hook'])
 | 
					  Rake::Task['db:migrate'].enhance(['db:post_migration_hook'])
 | 
				
			||||||
 | 
					 | 
				
			||||||
  # Before we load the schema, define the timestamp_id function.
 | 
					 | 
				
			||||||
  # Idiomatically, we might do this in a migration, but then it
 | 
					 | 
				
			||||||
  # wouldn't end up in schema.rb, so we'd need to figure out a way to
 | 
					 | 
				
			||||||
  # get it in before doing db:setup as well. This is simpler, and
 | 
					 | 
				
			||||||
  # ensures it's always in place.
 | 
					 | 
				
			||||||
  Rake::Task['db:schema:load'].enhance ['db:define_timestamp_id']
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  # After we load the schema, make sure we have sequences for each
 | 
					 | 
				
			||||||
  # table using timestamp IDs.
 | 
					 | 
				
			||||||
  Rake::Task['db:schema:load'].enhance do
 | 
					 | 
				
			||||||
    Rake::Task['db:ensure_id_sequences_exist'].invoke
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  task :define_timestamp_id do
 | 
					 | 
				
			||||||
    each_schema_load_environment do
 | 
					 | 
				
			||||||
      Mastodon::Snowflake.define_timestamp_id
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  task :ensure_id_sequences_exist do
 | 
					 | 
				
			||||||
    each_schema_load_environment do
 | 
					 | 
				
			||||||
      Mastodon::Snowflake.ensure_id_sequences_exist
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										14
									
								
								package.json
								
								
								
								
							
							
						
						
									
										14
									
								
								package.json
								
								
								
								
							| 
						 | 
					@ -60,14 +60,14 @@
 | 
				
			||||||
  },
 | 
					  },
 | 
				
			||||||
  "private": true,
 | 
					  "private": true,
 | 
				
			||||||
  "dependencies": {
 | 
					  "dependencies": {
 | 
				
			||||||
    "@babel/core": "^7.13.8",
 | 
					    "@babel/core": "^7.13.10",
 | 
				
			||||||
    "@babel/plugin-proposal-class-properties": "^7.8.3",
 | 
					    "@babel/plugin-proposal-class-properties": "^7.8.3",
 | 
				
			||||||
    "@babel/plugin-proposal-decorators": "^7.13.5",
 | 
					    "@babel/plugin-proposal-decorators": "^7.13.5",
 | 
				
			||||||
    "@babel/plugin-transform-react-inline-elements": "^7.12.13",
 | 
					    "@babel/plugin-transform-react-inline-elements": "^7.12.13",
 | 
				
			||||||
    "@babel/plugin-transform-runtime": "^7.13.9",
 | 
					    "@babel/plugin-transform-runtime": "^7.13.10",
 | 
				
			||||||
    "@babel/preset-env": "^7.13.9",
 | 
					    "@babel/preset-env": "^7.13.10",
 | 
				
			||||||
    "@babel/preset-react": "^7.12.13",
 | 
					    "@babel/preset-react": "^7.12.13",
 | 
				
			||||||
    "@babel/runtime": "^7.13.9",
 | 
					    "@babel/runtime": "^7.13.10",
 | 
				
			||||||
    "@clusterws/cws": "^3.0.0",
 | 
					    "@clusterws/cws": "^3.0.0",
 | 
				
			||||||
    "@gamestdio/websocket": "^0.3.2",
 | 
					    "@gamestdio/websocket": "^0.3.2",
 | 
				
			||||||
    "@github/webauthn-json": "^0.5.7",
 | 
					    "@github/webauthn-json": "^0.5.7",
 | 
				
			||||||
| 
						 | 
					@ -88,7 +88,7 @@
 | 
				
			||||||
    "color-blend": "^3.0.1",
 | 
					    "color-blend": "^3.0.1",
 | 
				
			||||||
    "compression-webpack-plugin": "^6.1.1",
 | 
					    "compression-webpack-plugin": "^6.1.1",
 | 
				
			||||||
    "cross-env": "^7.0.3",
 | 
					    "cross-env": "^7.0.3",
 | 
				
			||||||
    "css-loader": "^5.1.1",
 | 
					    "css-loader": "^5.1.2",
 | 
				
			||||||
    "cssnano": "^4.1.10",
 | 
					    "cssnano": "^4.1.10",
 | 
				
			||||||
    "detect-passive-events": "^2.0.3",
 | 
					    "detect-passive-events": "^2.0.3",
 | 
				
			||||||
    "dotenv": "^8.2.0",
 | 
					    "dotenv": "^8.2.0",
 | 
				
			||||||
| 
						 | 
					@ -146,7 +146,7 @@
 | 
				
			||||||
    "react-sparklines": "^1.7.0",
 | 
					    "react-sparklines": "^1.7.0",
 | 
				
			||||||
    "react-swipeable-views": "^0.13.9",
 | 
					    "react-swipeable-views": "^0.13.9",
 | 
				
			||||||
    "react-textarea-autosize": "^8.3.2",
 | 
					    "react-textarea-autosize": "^8.3.2",
 | 
				
			||||||
    "react-toggle": "^4.1.1",
 | 
					    "react-toggle": "^4.1.2",
 | 
				
			||||||
    "redis": "^3.0.2",
 | 
					    "redis": "^3.0.2",
 | 
				
			||||||
    "redux": "^4.0.5",
 | 
					    "redux": "^4.0.5",
 | 
				
			||||||
    "redux-immutable": "^4.0.0",
 | 
					    "redux-immutable": "^4.0.0",
 | 
				
			||||||
| 
						 | 
					@ -179,7 +179,7 @@
 | 
				
			||||||
    "@testing-library/react": "^11.2.5",
 | 
					    "@testing-library/react": "^11.2.5",
 | 
				
			||||||
    "babel-eslint": "^10.1.0",
 | 
					    "babel-eslint": "^10.1.0",
 | 
				
			||||||
    "babel-jest": "^26.6.3",
 | 
					    "babel-jest": "^26.6.3",
 | 
				
			||||||
    "eslint": "^7.21.0",
 | 
					    "eslint": "^7.22.0",
 | 
				
			||||||
    "eslint-plugin-import": "~2.22.1",
 | 
					    "eslint-plugin-import": "~2.22.1",
 | 
				
			||||||
    "eslint-plugin-jsx-a11y": "~6.4.1",
 | 
					    "eslint-plugin-jsx-a11y": "~6.4.1",
 | 
				
			||||||
    "eslint-plugin-promise": "~4.3.1",
 | 
					    "eslint-plugin-promise": "~4.3.1",
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -370,7 +370,7 @@ RSpec.describe AccountsController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it_behaves_like 'cachable response'
 | 
					        it_behaves_like 'cachable response'
 | 
				
			||||||
| 
						 | 
					@ -402,7 +402,7 @@ RSpec.describe AccountsController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns public Cache-Control header' do
 | 
					        it 'returns public Cache-Control header' do
 | 
				
			||||||
| 
						 | 
					@ -428,7 +428,7 @@ RSpec.describe AccountsController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it_behaves_like 'cachable response'
 | 
					        it_behaves_like 'cachable response'
 | 
				
			||||||
| 
						 | 
					@ -446,7 +446,7 @@ RSpec.describe AccountsController, type: :controller do
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it 'returns application/activity+json' do
 | 
					          it 'returns application/activity+json' do
 | 
				
			||||||
            expect(response.content_type).to eq 'application/activity+json'
 | 
					            expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it 'returns private Cache-Control header' do
 | 
					          it 'returns private Cache-Control header' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -43,7 +43,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it_behaves_like 'cachable response'
 | 
					        it_behaves_like 'cachable response'
 | 
				
			||||||
| 
						 | 
					@ -88,7 +88,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it 'returns application/activity+json' do
 | 
					          it 'returns application/activity+json' do
 | 
				
			||||||
            expect(response.content_type).to eq 'application/activity+json'
 | 
					            expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it_behaves_like 'cachable response'
 | 
					          it_behaves_like 'cachable response'
 | 
				
			||||||
| 
						 | 
					@ -116,7 +116,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'returns application/activity+json' do
 | 
					            it 'returns application/activity+json' do
 | 
				
			||||||
              expect(response.content_type).to eq 'application/activity+json'
 | 
					              expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'returns private Cache-Control header' do
 | 
					            it 'returns private Cache-Control header' do
 | 
				
			||||||
| 
						 | 
					@ -141,7 +141,7 @@ RSpec.describe ActivityPub::CollectionsController, type: :controller do
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'returns application/activity+json' do
 | 
					            it 'returns application/activity+json' do
 | 
				
			||||||
              expect(response.content_type).to eq 'application/activity+json'
 | 
					              expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
            end
 | 
					            end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
            it 'returns private Cache-Control header' do
 | 
					            it 'returns private Cache-Control header' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -40,7 +40,7 @@ RSpec.describe ActivityPub::FollowersSynchronizationsController, type: :controll
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'returns application/activity+json' do
 | 
					      it 'returns application/activity+json' do
 | 
				
			||||||
        expect(response.content_type).to eq 'application/activity+json'
 | 
					        expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'returns orderedItems with followers from example.com' do
 | 
					      it 'returns orderedItems with followers from example.com' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -46,7 +46,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns totalItems' do
 | 
					        it 'returns totalItems' do
 | 
				
			||||||
| 
						 | 
					@ -85,7 +85,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns orderedItems with public or unlisted statuses' do
 | 
					        it 'returns orderedItems with public or unlisted statuses' do
 | 
				
			||||||
| 
						 | 
					@ -133,7 +133,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns orderedItems with public or unlisted statuses' do
 | 
					        it 'returns orderedItems with public or unlisted statuses' do
 | 
				
			||||||
| 
						 | 
					@ -159,7 +159,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns orderedItems with private statuses' do
 | 
					        it 'returns orderedItems with private statuses' do
 | 
				
			||||||
| 
						 | 
					@ -185,7 +185,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns empty orderedItems' do
 | 
					        it 'returns empty orderedItems' do
 | 
				
			||||||
| 
						 | 
					@ -210,7 +210,7 @@ RSpec.describe ActivityPub::OutboxesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns empty orderedItems' do
 | 
					        it 'returns empty orderedItems' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -73,7 +73,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns application/activity+json' do
 | 
					        it 'returns application/activity+json' do
 | 
				
			||||||
          expect(response.content_type).to eq 'application/activity+json'
 | 
					          expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it_behaves_like 'cachable response'
 | 
					        it_behaves_like 'cachable response'
 | 
				
			||||||
| 
						 | 
					@ -120,7 +120,7 @@ RSpec.describe ActivityPub::RepliesController, type: :controller do
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it 'returns application/activity+json' do
 | 
					          it 'returns application/activity+json' do
 | 
				
			||||||
            expect(response.content_type).to eq 'application/activity+json'
 | 
					            expect(response.media_type).to eq 'application/activity+json'
 | 
				
			||||||
          end
 | 
					          end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
          it_behaves_like 'cachable response'
 | 
					          it_behaves_like 'cachable response'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,40 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe CacheConcern, type: :controller do
 | 
				
			||||||
 | 
					  controller(ApplicationController) do
 | 
				
			||||||
 | 
					    include CacheConcern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def empty_array
 | 
				
			||||||
 | 
					      render plain: cache_collection([], Status).size
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    def empty_relation
 | 
				
			||||||
 | 
					      render plain: cache_collection(Status.none, Status).size
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  before do
 | 
				
			||||||
 | 
					    routes.draw do
 | 
				
			||||||
 | 
					      get  'empty_array' => 'anonymous#empty_array'
 | 
				
			||||||
 | 
					      post 'empty_relation' => 'anonymous#empty_relation'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#cache_collection' do
 | 
				
			||||||
 | 
					    context 'given an empty array' do
 | 
				
			||||||
 | 
					      it 'returns an empty array' do
 | 
				
			||||||
 | 
					        get :empty_array
 | 
				
			||||||
 | 
					        expect(response.body).to eq '0'
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'given an empty relation' do
 | 
				
			||||||
 | 
					      it 'returns an empty array' do
 | 
				
			||||||
 | 
					        get :empty_relation
 | 
				
			||||||
 | 
					        expect(response.body).to eq '0'
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -22,8 +22,8 @@ describe ApplicationController, type: :controller do
 | 
				
			||||||
      get :index, format: :csv
 | 
					      get :index, format: :csv
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'text/csv'
 | 
					      expect(response.media_type).to eq 'text/csv'
 | 
				
			||||||
      expect(response.headers['Content-Disposition']).to eq 'attachment; filename="anonymous.csv"'
 | 
					      expect(response.headers['Content-Disposition']).to start_with 'attachment; filename="anonymous.csv"'
 | 
				
			||||||
      expect(response.body).to eq user.account.username
 | 
					      expect(response.body).to eq user.account.username
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ describe WellKnown::HostMetaController, type: :controller do
 | 
				
			||||||
      get :show, format: :xml
 | 
					      get :show, format: :xml
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/xrd+xml'
 | 
					      expect(response.media_type).to eq 'application/xrd+xml'
 | 
				
			||||||
      expect(response.body).to eq <<XML
 | 
					      expect(response.body).to eq <<XML
 | 
				
			||||||
<?xml version="1.0" encoding="UTF-8"?>
 | 
					<?xml version="1.0" encoding="UTF-8"?>
 | 
				
			||||||
<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
 | 
					<XRD xmlns="http://docs.oasis-open.org/ns/xri/xrd-1.0">
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ describe WellKnown::KeybaseProofConfigController, type: :controller do
 | 
				
			||||||
      get :show
 | 
					      get :show
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/json'
 | 
					      expect(response.media_type).to eq 'application/json'
 | 
				
			||||||
      expect { JSON.parse(response.body) }.not_to raise_exception
 | 
					      expect { JSON.parse(response.body) }.not_to raise_exception
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ describe WellKnown::NodeInfoController, type: :controller do
 | 
				
			||||||
      get :index
 | 
					      get :index
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/json'
 | 
					      expect(response.media_type).to eq 'application/json'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      json = body_as_json
 | 
					      json = body_as_json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,7 +23,7 @@ describe WellKnown::NodeInfoController, type: :controller do
 | 
				
			||||||
      get :show
 | 
					      get :show
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/json'
 | 
					      expect(response.media_type).to eq 'application/json'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      json = body_as_json
 | 
					      json = body_as_json
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@ describe WellKnown::WebfingerController, type: :controller do
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'returns application/jrd+json' do
 | 
					      it 'returns application/jrd+json' do
 | 
				
			||||||
        expect(response.content_type).to eq 'application/jrd+json'
 | 
					        expect(response.media_type).to eq 'application/jrd+json'
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'returns links for the account' do
 | 
					      it 'returns links for the account' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,19 @@
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					RSpec.describe EntityCache do
 | 
				
			||||||
 | 
					  let(:local_account)  { Fabricate(:account, domain: nil, username: 'alice') }
 | 
				
			||||||
 | 
					  let(:remote_account) { Fabricate(:account, domain: 'remote.test', username: 'bob', url: 'https://remote.test/') }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#emoji' do
 | 
				
			||||||
 | 
					    subject { EntityCache.instance.emoji(shortcodes, domain) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    context 'called with an empty list of shortcodes' do
 | 
				
			||||||
 | 
					      let(:shortcodes) { [] }
 | 
				
			||||||
 | 
					      let(:domain)     { 'example.org' }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      it 'returns an empty array' do
 | 
				
			||||||
 | 
					        is_expected.to eq []
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,7 +1,6 @@
 | 
				
			||||||
# frozen_string_literal: true
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require 'rails_helper'
 | 
					require 'rails_helper'
 | 
				
			||||||
require Rails.root.join('app', 'lib', 'sanitize_config.rb')
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
describe Sanitize::Config do
 | 
					describe Sanitize::Config do
 | 
				
			||||||
  shared_examples 'common HTML sanitization' do
 | 
					  shared_examples 'common HTML sanitization' do
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,57 +0,0 @@
 | 
				
			||||||
require 'rails_helper'
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
RSpec.describe AccountStat, type: :model do
 | 
					 | 
				
			||||||
  describe '#increment_count!' do
 | 
					 | 
				
			||||||
    it 'increments the count' do
 | 
					 | 
				
			||||||
      account_stat = AccountStat.create(account: Fabricate(:account))
 | 
					 | 
				
			||||||
      expect(account_stat.followers_count).to eq 0
 | 
					 | 
				
			||||||
      account_stat.increment_count!(:followers_count)
 | 
					 | 
				
			||||||
      expect(account_stat.followers_count).to eq 1
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'increments the count in multi-threaded an environment' do
 | 
					 | 
				
			||||||
      account_stat   = AccountStat.create(account: Fabricate(:account), statuses_count: 0)
 | 
					 | 
				
			||||||
      increment_by   = 15
 | 
					 | 
				
			||||||
      wait_for_start = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      threads = Array.new(increment_by) do
 | 
					 | 
				
			||||||
        Thread.new do
 | 
					 | 
				
			||||||
          true while wait_for_start
 | 
					 | 
				
			||||||
          AccountStat.find(account_stat.id).increment_count!(:statuses_count)
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      wait_for_start = false
 | 
					 | 
				
			||||||
      threads.each(&:join)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(account_stat.reload.statuses_count).to eq increment_by
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
  describe '#decrement_count!' do
 | 
					 | 
				
			||||||
    it 'decrements the count' do
 | 
					 | 
				
			||||||
      account_stat = AccountStat.create(account: Fabricate(:account), followers_count: 15)
 | 
					 | 
				
			||||||
      expect(account_stat.followers_count).to eq 15
 | 
					 | 
				
			||||||
      account_stat.decrement_count!(:followers_count)
 | 
					 | 
				
			||||||
      expect(account_stat.followers_count).to eq 14
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    it 'decrements the count in multi-threaded an environment' do
 | 
					 | 
				
			||||||
      account_stat   = AccountStat.create(account: Fabricate(:account), statuses_count: 15)
 | 
					 | 
				
			||||||
      decrement_by   = 10
 | 
					 | 
				
			||||||
      wait_for_start = true
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      threads = Array.new(decrement_by) do
 | 
					 | 
				
			||||||
        Thread.new do
 | 
					 | 
				
			||||||
          true while wait_for_start
 | 
					 | 
				
			||||||
          AccountStat.find(account_stat.id).decrement_count!(:statuses_count)
 | 
					 | 
				
			||||||
        end
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      wait_for_start = false
 | 
					 | 
				
			||||||
      threads.each(&:join)
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      expect(account_stat.reload.statuses_count).to eq 5
 | 
					 | 
				
			||||||
    end
 | 
					 | 
				
			||||||
  end
 | 
					 | 
				
			||||||
end
 | 
					 | 
				
			||||||
| 
						 | 
					@ -0,0 +1,60 @@
 | 
				
			||||||
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					describe AccountCounters do
 | 
				
			||||||
 | 
					  let!(:account) { Fabricate(:account) }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#increment_count!' do
 | 
				
			||||||
 | 
					    it 'increments the count' do
 | 
				
			||||||
 | 
					      expect(account.followers_count).to eq 0
 | 
				
			||||||
 | 
					      account.increment_count!(:followers_count)
 | 
				
			||||||
 | 
					      expect(account.followers_count).to eq 1
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'increments the count in multi-threaded an environment' do
 | 
				
			||||||
 | 
					      increment_by   = 15
 | 
				
			||||||
 | 
					      wait_for_start = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      threads = Array.new(increment_by) do
 | 
				
			||||||
 | 
					        Thread.new do
 | 
				
			||||||
 | 
					          true while wait_for_start
 | 
				
			||||||
 | 
					          account.increment_count!(:statuses_count)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      wait_for_start = false
 | 
				
			||||||
 | 
					      threads.each(&:join)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(account.statuses_count).to eq increment_by
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  describe '#decrement_count!' do
 | 
				
			||||||
 | 
					    it 'decrements the count' do
 | 
				
			||||||
 | 
					      account.followers_count = 15
 | 
				
			||||||
 | 
					      account.save!
 | 
				
			||||||
 | 
					      expect(account.followers_count).to eq 15
 | 
				
			||||||
 | 
					      account.decrement_count!(:followers_count)
 | 
				
			||||||
 | 
					      expect(account.followers_count).to eq 14
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'decrements the count in multi-threaded an environment' do
 | 
				
			||||||
 | 
					      decrement_by   = 10
 | 
				
			||||||
 | 
					      wait_for_start = true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      account.statuses_count = 15
 | 
				
			||||||
 | 
					      account.save!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      threads = Array.new(decrement_by) do
 | 
				
			||||||
 | 
					        Thread.new do
 | 
				
			||||||
 | 
					          true while wait_for_start
 | 
				
			||||||
 | 
					          account.decrement_count!(:statuses_count)
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      wait_for_start = false
 | 
				
			||||||
 | 
					      threads.each(&:join)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(account.statuses_count).to eq 5
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ describe "The catch all route" do
 | 
				
			||||||
      get "/test"
 | 
					      get "/test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response.status).to eq 404
 | 
					      expect(response.status).to eq 404
 | 
				
			||||||
      expect(response.content_type).to eq "text/html"
 | 
					      expect(response.media_type).to eq "text/html"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -15,7 +15,7 @@ describe "The catch all route" do
 | 
				
			||||||
      get "/test.test"
 | 
					      get "/test.test"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response.status).to eq 404
 | 
					      expect(response.status).to eq 404
 | 
				
			||||||
      expect(response.content_type).to eq "text/html"
 | 
					      expect(response.media_type).to eq "text/html"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -6,7 +6,7 @@ describe "The host_meta route" do
 | 
				
			||||||
      get host_meta_url
 | 
					      get host_meta_url
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq "application/xrd+xml"
 | 
					      expect(response.media_type).to eq "application/xrd+xml"
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@ describe 'Link headers' do
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    def link_header_with_type(type)
 | 
					    def link_header_with_type(type)
 | 
				
			||||||
      response.headers['Link'].links.find do |link|
 | 
					      LinkHeader.parse(response.headers['Link'].to_s).links.find do |link|
 | 
				
			||||||
        link.attr_pairs.any? { |pair| pair == ['type', type] }
 | 
					        link.attr_pairs.any? { |pair| pair == ['type', type] }
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ describe 'The webfinger route' do
 | 
				
			||||||
      get webfinger_url(resource: alice.to_webfinger_s)
 | 
					      get webfinger_url(resource: alice.to_webfinger_s)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/jrd+json'
 | 
					      expect(response.media_type).to eq 'application/jrd+json'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -17,7 +17,7 @@ describe 'The webfinger route' do
 | 
				
			||||||
      get webfinger_url(resource: alice.to_webfinger_s, format: :json)
 | 
					      get webfinger_url(resource: alice.to_webfinger_s, format: :json)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/jrd+json'
 | 
					      expect(response.media_type).to eq 'application/jrd+json'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    it 'returns a json response for json accept header' do
 | 
					    it 'returns a json response for json accept header' do
 | 
				
			||||||
| 
						 | 
					@ -25,7 +25,7 @@ describe 'The webfinger route' do
 | 
				
			||||||
      get webfinger_url(resource: alice.to_webfinger_s), headers: headers
 | 
					      get webfinger_url(resource: alice.to_webfinger_s), headers: headers
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(response).to have_http_status(200)
 | 
					      expect(response).to have_http_status(200)
 | 
				
			||||||
      expect(response.content_type).to eq 'application/jrd+json'
 | 
					      expect(response.media_type).to eq 'application/jrd+json'
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
require 'rails_helper'
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RSpec.describe UrlValidator, type: :validator do
 | 
					RSpec.describe URLValidator, type: :validator do
 | 
				
			||||||
  describe '#validate_each' do
 | 
					  describe '#validate_each' do
 | 
				
			||||||
    before do
 | 
					    before do
 | 
				
			||||||
      allow(validator).to receive(:compliant?).with(value) { compliant }
 | 
					      allow(validator).to receive(:compliant?).with(value) { compliant }
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										131
									
								
								yarn.lock
								
								
								
								
							
							
						
						
									
										131
									
								
								yarn.lock
								
								
								
								
							| 
						 | 
					@ -21,17 +21,17 @@
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.8.tgz#5b783b9808f15cef71547f1b691f34f8ff6003a6"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/compat-data/-/compat-data-7.13.8.tgz#5b783b9808f15cef71547f1b691f34f8ff6003a6"
 | 
				
			||||||
  integrity sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog==
 | 
					  integrity sha512-EaI33z19T4qN3xLXsGf48M2cDqa6ei9tPZlfLdb2HC+e/cFtREiRd8hdSqDbwdLB0/+gLwqJmCYASH0z2bUdog==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/core@^7.1.0", "@babel/core@^7.13.8", "@babel/core@^7.7.2", "@babel/core@^7.7.5":
 | 
					"@babel/core@^7.1.0", "@babel/core@^7.13.10", "@babel/core@^7.7.2", "@babel/core@^7.7.5":
 | 
				
			||||||
  version "7.13.8"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.8.tgz#c191d9c5871788a591d69ea1dc03e5843a3680fb"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/core/-/core-7.13.10.tgz#07de050bbd8193fcd8a3c27918c0890613a94559"
 | 
				
			||||||
  integrity sha512-oYapIySGw1zGhEFRd6lzWNLWFX2s5dA/jm+Pw/+59ZdXtjyIuwlXbrId22Md0rgZVop+aVoqow2riXhBLNyuQg==
 | 
					  integrity sha512-bfIYcT0BdKeAZrovpMqX2Mx5NrgAckGbwT982AkdS5GNfn3KMGiprlBAtmBcFZRUmpaufS6WZFP8trvx8ptFDw==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/code-frame" "^7.12.13"
 | 
					    "@babel/code-frame" "^7.12.13"
 | 
				
			||||||
    "@babel/generator" "^7.13.0"
 | 
					    "@babel/generator" "^7.13.9"
 | 
				
			||||||
    "@babel/helper-compilation-targets" "^7.13.8"
 | 
					    "@babel/helper-compilation-targets" "^7.13.10"
 | 
				
			||||||
    "@babel/helper-module-transforms" "^7.13.0"
 | 
					    "@babel/helper-module-transforms" "^7.13.0"
 | 
				
			||||||
    "@babel/helpers" "^7.13.0"
 | 
					    "@babel/helpers" "^7.13.10"
 | 
				
			||||||
    "@babel/parser" "^7.13.4"
 | 
					    "@babel/parser" "^7.13.10"
 | 
				
			||||||
    "@babel/template" "^7.12.13"
 | 
					    "@babel/template" "^7.12.13"
 | 
				
			||||||
    "@babel/traverse" "^7.13.0"
 | 
					    "@babel/traverse" "^7.13.0"
 | 
				
			||||||
    "@babel/types" "^7.13.0"
 | 
					    "@babel/types" "^7.13.0"
 | 
				
			||||||
| 
						 | 
					@ -43,10 +43,10 @@
 | 
				
			||||||
    semver "^6.3.0"
 | 
					    semver "^6.3.0"
 | 
				
			||||||
    source-map "^0.5.0"
 | 
					    source-map "^0.5.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/generator@^7.13.0":
 | 
					"@babel/generator@^7.13.0", "@babel/generator@^7.13.9":
 | 
				
			||||||
  version "7.13.0"
 | 
					  version "7.13.9"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.0.tgz#bd00d4394ca22f220390c56a0b5b85568ec1ec0c"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/generator/-/generator-7.13.9.tgz#3a7aa96f9efb8e2be42d38d80e2ceb4c64d8de39"
 | 
				
			||||||
  integrity sha512-zBZfgvBB/ywjx0Rgc2+BwoH/3H+lDtlgD4hBOpEv5LxRnYsm/753iRuLepqnYlynpjC3AdQxtxsoeHJoEEwOAw==
 | 
					  integrity sha512-mHOOmY0Axl/JCTkxTU6Lf5sWOg/v8nUa+Xkt4zMTftX0wqmb6Sh7J8gvcehBw7q0AhrhAR+FDacKjCZ2X8K+Sw==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/types" "^7.13.0"
 | 
					    "@babel/types" "^7.13.0"
 | 
				
			||||||
    jsesc "^2.5.1"
 | 
					    jsesc "^2.5.1"
 | 
				
			||||||
| 
						 | 
					@ -82,10 +82,10 @@
 | 
				
			||||||
    "@babel/helper-annotate-as-pure" "^7.12.13"
 | 
					    "@babel/helper-annotate-as-pure" "^7.12.13"
 | 
				
			||||||
    "@babel/types" "^7.12.13"
 | 
					    "@babel/types" "^7.12.13"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.8":
 | 
					"@babel/helper-compilation-targets@^7.13.0", "@babel/helper-compilation-targets@^7.13.10", "@babel/helper-compilation-targets@^7.13.8":
 | 
				
			||||||
  version "7.13.8"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.8.tgz#02bdb22783439afb11b2f009814bdd88384bd468"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/helper-compilation-targets/-/helper-compilation-targets-7.13.10.tgz#1310a1678cb8427c07a753750da4f8ce442bdd0c"
 | 
				
			||||||
  integrity sha512-pBljUGC1y3xKLn1nrx2eAhurLMA8OqBtBP/JwG4U8skN7kf8/aqwwxpV1N6T0e7r6+7uNitIa/fUxPFagSXp3A==
 | 
					  integrity sha512-/Xju7Qg1GQO4mHZ/Kcs6Au7gfafgZnwm+a7sy/ow/tV1sHeraRUHbjdat8/UvDor4Tez+siGKDk6zIKtCPKVJA==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/compat-data" "^7.13.8"
 | 
					    "@babel/compat-data" "^7.13.8"
 | 
				
			||||||
    "@babel/helper-validator-option" "^7.12.17"
 | 
					    "@babel/helper-validator-option" "^7.12.17"
 | 
				
			||||||
| 
						 | 
					@ -274,10 +274,10 @@
 | 
				
			||||||
    "@babel/traverse" "^7.13.0"
 | 
					    "@babel/traverse" "^7.13.0"
 | 
				
			||||||
    "@babel/types" "^7.13.0"
 | 
					    "@babel/types" "^7.13.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/helpers@^7.13.0":
 | 
					"@babel/helpers@^7.13.10":
 | 
				
			||||||
  version "7.13.0"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.0.tgz#7647ae57377b4f0408bf4f8a7af01c42e41badc0"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/helpers/-/helpers-7.13.10.tgz#fd8e2ba7488533cdeac45cc158e9ebca5e3c7df8"
 | 
				
			||||||
  integrity sha512-aan1MeFPxFacZeSz6Ld7YZo5aPuqnKlD7+HZY75xQsueczFccP9A7V05+oe0XpLwHK3oLorPe9eaAUljL7WEaQ==
 | 
					  integrity sha512-4VO883+MWPDUVRF3PhiLBUFHoX/bsLTGFpFK/HqvvfBZz2D57u9XzPVNFVBTc0PW/CWR9BXTOKt8NF4DInUHcQ==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/template" "^7.12.13"
 | 
					    "@babel/template" "^7.12.13"
 | 
				
			||||||
    "@babel/traverse" "^7.13.0"
 | 
					    "@babel/traverse" "^7.13.0"
 | 
				
			||||||
| 
						 | 
					@ -292,10 +292,10 @@
 | 
				
			||||||
    chalk "^2.0.0"
 | 
					    chalk "^2.0.0"
 | 
				
			||||||
    js-tokens "^4.0.0"
 | 
					    js-tokens "^4.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.13.0", "@babel/parser@^7.13.4", "@babel/parser@^7.7.0":
 | 
					"@babel/parser@^7.1.0", "@babel/parser@^7.12.13", "@babel/parser@^7.13.0", "@babel/parser@^7.13.10", "@babel/parser@^7.7.0":
 | 
				
			||||||
  version "7.13.4"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.4.tgz#340211b0da94a351a6f10e63671fa727333d13ab"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/parser/-/parser-7.13.10.tgz#8f8f9bf7b3afa3eabd061f7a5bcdf4fec3c48409"
 | 
				
			||||||
  integrity sha512-uvoOulWHhI+0+1f9L4BoozY7U5cIkZ9PgJqvb041d6vypgUmtVPG4vmGm4pSggjl8BELzvHyUeJSUyEMY6b+qA==
 | 
					  integrity sha512-0s7Mlrw9uTWkYua7xWr99Wpk2bnGa0ANleKfksYAES8LpWH4gW1OUr42vqKNf0us5UQNfru2wPqMqRITzq/SIQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/plugin-proposal-async-generator-functions@^7.13.8":
 | 
					"@babel/plugin-proposal-async-generator-functions@^7.13.8":
 | 
				
			||||||
  version "7.13.8"
 | 
					  version "7.13.8"
 | 
				
			||||||
| 
						 | 
					@ -765,10 +765,10 @@
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/helper-plugin-utils" "^7.12.13"
 | 
					    "@babel/helper-plugin-utils" "^7.12.13"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/plugin-transform-runtime@^7.13.9":
 | 
					"@babel/plugin-transform-runtime@^7.13.10":
 | 
				
			||||||
  version "7.13.9"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.9.tgz#744d3103338a0d6c90dee0497558150b490cee07"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.13.10.tgz#a1e40d22e2bf570c591c9c7e5ab42d6bf1e419e1"
 | 
				
			||||||
  integrity sha512-XCxkY/wBI6M6Jj2mlWxkmqbKPweRanszWbF3Tyut+hKh+PHcuIH/rSr/7lmmE7C3WW+HSIm2GT+d5jwmheuB0g==
 | 
					  integrity sha512-Y5k8ipgfvz5d/76tx7JYbKQTcgFSU6VgJ3kKQv4zGTKr+a9T/KBvfRvGtSFgKDQGt/DBykQixV0vNWKIdzWErA==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/helper-module-imports" "^7.12.13"
 | 
					    "@babel/helper-module-imports" "^7.12.13"
 | 
				
			||||||
    "@babel/helper-plugin-utils" "^7.13.0"
 | 
					    "@babel/helper-plugin-utils" "^7.13.0"
 | 
				
			||||||
| 
						 | 
					@ -828,13 +828,13 @@
 | 
				
			||||||
    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
 | 
					    "@babel/helper-create-regexp-features-plugin" "^7.12.13"
 | 
				
			||||||
    "@babel/helper-plugin-utils" "^7.12.13"
 | 
					    "@babel/helper-plugin-utils" "^7.12.13"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/preset-env@^7.13.9":
 | 
					"@babel/preset-env@^7.13.10":
 | 
				
			||||||
  version "7.13.9"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.9.tgz#3ee5f233316b10d066d7f379c6d1e13a96853654"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/preset-env/-/preset-env-7.13.10.tgz#b5cde31d5fe77ab2a6ab3d453b59041a1b3a5252"
 | 
				
			||||||
  integrity sha512-mcsHUlh2rIhViqMG823JpscLMesRt3QbMsv1+jhopXEb3W2wXvQ9QoiOlZI9ZbR3XqPtaFpZwEZKYqGJnGMZTQ==
 | 
					  integrity sha512-nOsTScuoRghRtUsRr/c69d042ysfPHcu+KOB4A9aAO9eJYqrkat+LF8G1yp1HD18QiwixT2CisZTr/0b3YZPXQ==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/compat-data" "^7.13.8"
 | 
					    "@babel/compat-data" "^7.13.8"
 | 
				
			||||||
    "@babel/helper-compilation-targets" "^7.13.8"
 | 
					    "@babel/helper-compilation-targets" "^7.13.10"
 | 
				
			||||||
    "@babel/helper-plugin-utils" "^7.13.0"
 | 
					    "@babel/helper-plugin-utils" "^7.13.0"
 | 
				
			||||||
    "@babel/helper-validator-option" "^7.12.17"
 | 
					    "@babel/helper-validator-option" "^7.12.17"
 | 
				
			||||||
    "@babel/plugin-proposal-async-generator-functions" "^7.13.8"
 | 
					    "@babel/plugin-proposal-async-generator-functions" "^7.13.8"
 | 
				
			||||||
| 
						 | 
					@ -939,10 +939,10 @@
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    regenerator-runtime "^0.12.0"
 | 
					    regenerator-runtime "^0.12.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.9", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
 | 
					"@babel/runtime@^7.1.2", "@babel/runtime@^7.10.2", "@babel/runtime@^7.11.2", "@babel/runtime@^7.12.1", "@babel/runtime@^7.12.5", "@babel/runtime@^7.13.10", "@babel/runtime@^7.2.0", "@babel/runtime@^7.3.1", "@babel/runtime@^7.4.4", "@babel/runtime@^7.5.5", "@babel/runtime@^7.6.3", "@babel/runtime@^7.7.2", "@babel/runtime@^7.8.4", "@babel/runtime@^7.9.2":
 | 
				
			||||||
  version "7.13.9"
 | 
					  version "7.13.10"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.9.tgz#97dbe2116e2630c489f22e0656decd60aaa1fcee"
 | 
					  resolved "https://registry.yarnpkg.com/@babel/runtime/-/runtime-7.13.10.tgz#47d42a57b6095f4468da440388fdbad8bebf0d7d"
 | 
				
			||||||
  integrity sha512-aY2kU+xgJ3dJ1eU6FMB9EH8dIe8dmusF1xEku52joLvw6eAFN0AI+WxCLDnpev2LEejWBAy2sBvBOBAjI3zmvA==
 | 
					  integrity sha512-4QPkjJq6Ns3V/RgpEahRk+AGfL0eO6RHHtTWoNNr5mO49G6B5+X6d6THgWEAvTrznU5xYpbAlVKRYcsCgh/Akw==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    regenerator-runtime "^0.13.4"
 | 
					    regenerator-runtime "^0.13.4"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -3060,6 +3060,11 @@ colorette@^1.2.1:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
 | 
					  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.1.tgz#4d0b921325c14faf92633086a536db6e89564b1b"
 | 
				
			||||||
  integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
 | 
					  integrity sha512-puCDz0CzydiSYOrnXpz/PKd69zRrribezjtE9yd4zvytoRc8+RY/KJPvtPFKZS3E3wP6neGyMe0vOTlHO5L3Pw==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					colorette@^1.2.2:
 | 
				
			||||||
 | 
					  version "1.2.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/colorette/-/colorette-1.2.2.tgz#cbcc79d5e99caea2dbf10eb3a26fd8b3e6acfa94"
 | 
				
			||||||
 | 
					  integrity sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
combined-stream@^1.0.6, combined-stream@~1.0.6:
 | 
					combined-stream@^1.0.6, combined-stream@~1.0.6:
 | 
				
			||||||
  version "1.0.8"
 | 
					  version "1.0.8"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
 | 
					  resolved "https://registry.yarnpkg.com/combined-stream/-/combined-stream-1.0.8.tgz#c3d45a8b34fd730631a110a8a2520682b31d5a7f"
 | 
				
			||||||
| 
						 | 
					@ -3373,16 +3378,16 @@ css-list-helpers@^1.0.1:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    tcomb "^2.5.0"
 | 
					    tcomb "^2.5.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
css-loader@^5.1.1:
 | 
					css-loader@^5.1.2:
 | 
				
			||||||
  version "5.1.1"
 | 
					  version "5.1.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.1.tgz#9362d444a0f7c08c148a109596715c904e252879"
 | 
					  resolved "https://registry.yarnpkg.com/css-loader/-/css-loader-5.1.2.tgz#b93dba498ec948b543b49d4fab5017205d4f5c3e"
 | 
				
			||||||
  integrity sha512-5FfhpjwtuRgxqmusDidowqmLlcb+1HgnEDMsi2JhiUrZUcoc+cqw+mUtMIF/+OfeMYaaFCLYp1TaIt9H6I/fKA==
 | 
					  integrity sha512-T7vTXHSx0KrVEg/xjcl7G01RcVXpcw4OELwDPvkr7izQNny85A84dK3dqrczuEfBcu7Yg7mdTjJLSTibRUoRZg==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    camelcase "^6.2.0"
 | 
					    camelcase "^6.2.0"
 | 
				
			||||||
    cssesc "^3.0.0"
 | 
					    cssesc "^3.0.0"
 | 
				
			||||||
    icss-utils "^5.1.0"
 | 
					    icss-utils "^5.1.0"
 | 
				
			||||||
    loader-utils "^2.0.0"
 | 
					    loader-utils "^2.0.0"
 | 
				
			||||||
    postcss "^8.2.6"
 | 
					    postcss "^8.2.8"
 | 
				
			||||||
    postcss-modules-extract-imports "^3.0.0"
 | 
					    postcss-modules-extract-imports "^3.0.0"
 | 
				
			||||||
    postcss-modules-local-by-default "^4.0.0"
 | 
					    postcss-modules-local-by-default "^4.0.0"
 | 
				
			||||||
    postcss-modules-scope "^3.0.0"
 | 
					    postcss-modules-scope "^3.0.0"
 | 
				
			||||||
| 
						 | 
					@ -4336,10 +4341,10 @@ eslint@^2.7.0:
 | 
				
			||||||
    text-table "~0.2.0"
 | 
					    text-table "~0.2.0"
 | 
				
			||||||
    user-home "^2.0.0"
 | 
					    user-home "^2.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
eslint@^7.21.0:
 | 
					eslint@^7.22.0:
 | 
				
			||||||
  version "7.21.0"
 | 
					  version "7.22.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.21.0.tgz#4ecd5b8c5b44f5dedc9b8a110b01bbfeb15d1c83"
 | 
					  resolved "https://registry.yarnpkg.com/eslint/-/eslint-7.22.0.tgz#07ecc61052fec63661a2cab6bd507127c07adc6f"
 | 
				
			||||||
  integrity sha512-W2aJbXpMNofUp0ztQaF40fveSsJBjlSCSWpy//gzfTvwC+USs/nceBrKmlJOiM8r1bLwP2EuYkCqArn/6QTIgg==
 | 
					  integrity sha512-3VawOtjSJUQiiqac8MQc+w457iGLfuNGLFn8JmF051tTKbh5/x/0vlcEj8OgDCaw7Ysa2Jn8paGshV7x2abKXg==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    "@babel/code-frame" "7.12.11"
 | 
					    "@babel/code-frame" "7.12.11"
 | 
				
			||||||
    "@eslint/eslintrc" "^0.4.0"
 | 
					    "@eslint/eslintrc" "^0.4.0"
 | 
				
			||||||
| 
						 | 
					@ -4358,7 +4363,7 @@ eslint@^7.21.0:
 | 
				
			||||||
    file-entry-cache "^6.0.1"
 | 
					    file-entry-cache "^6.0.1"
 | 
				
			||||||
    functional-red-black-tree "^1.0.1"
 | 
					    functional-red-black-tree "^1.0.1"
 | 
				
			||||||
    glob-parent "^5.0.0"
 | 
					    glob-parent "^5.0.0"
 | 
				
			||||||
    globals "^12.1.0"
 | 
					    globals "^13.6.0"
 | 
				
			||||||
    ignore "^4.0.6"
 | 
					    ignore "^4.0.6"
 | 
				
			||||||
    import-fresh "^3.0.0"
 | 
					    import-fresh "^3.0.0"
 | 
				
			||||||
    imurmurhash "^0.1.4"
 | 
					    imurmurhash "^0.1.4"
 | 
				
			||||||
| 
						 | 
					@ -4366,7 +4371,7 @@ eslint@^7.21.0:
 | 
				
			||||||
    js-yaml "^3.13.1"
 | 
					    js-yaml "^3.13.1"
 | 
				
			||||||
    json-stable-stringify-without-jsonify "^1.0.1"
 | 
					    json-stable-stringify-without-jsonify "^1.0.1"
 | 
				
			||||||
    levn "^0.4.1"
 | 
					    levn "^0.4.1"
 | 
				
			||||||
    lodash "^4.17.20"
 | 
					    lodash "^4.17.21"
 | 
				
			||||||
    minimatch "^3.0.4"
 | 
					    minimatch "^3.0.4"
 | 
				
			||||||
    natural-compare "^1.4.0"
 | 
					    natural-compare "^1.4.0"
 | 
				
			||||||
    optionator "^0.9.1"
 | 
					    optionator "^0.9.1"
 | 
				
			||||||
| 
						 | 
					@ -5117,6 +5122,13 @@ globals@^12.1.0:
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    type-fest "^0.8.1"
 | 
					    type-fest "^0.8.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					globals@^13.6.0:
 | 
				
			||||||
 | 
					  version "13.6.0"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/globals/-/globals-13.6.0.tgz#d77138e53738567bb96a3916ff6f6b487af20ef7"
 | 
				
			||||||
 | 
					  integrity sha512-YFKCX0SiPg7l5oKYCJ2zZGxcXprVXHcSnVuvzrT3oSENQonVLqM5pf9fN5dLGZGyCjhw8TN8Btwe/jKnZ0pjvQ==
 | 
				
			||||||
 | 
					  dependencies:
 | 
				
			||||||
 | 
					    type-fest "^0.20.2"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
globals@^9.2.0:
 | 
					globals@^9.2.0:
 | 
				
			||||||
  version "9.18.0"
 | 
					  version "9.18.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
 | 
					  resolved "https://registry.yarnpkg.com/globals/-/globals-9.18.0.tgz#aa3896b3e69b487f17e31ed2143d69a8e30c2d8a"
 | 
				
			||||||
| 
						 | 
					@ -8484,12 +8496,12 @@ postcss@^7.0.0, postcss@^7.0.1, postcss@^7.0.27, postcss@^7.0.32:
 | 
				
			||||||
    source-map "^0.6.1"
 | 
					    source-map "^0.6.1"
 | 
				
			||||||
    supports-color "^6.1.0"
 | 
					    supports-color "^6.1.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
postcss@^8.2.6:
 | 
					postcss@^8.2.8:
 | 
				
			||||||
  version "8.2.6"
 | 
					  version "8.2.8"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.6.tgz#5d69a974543b45f87e464bc4c3e392a97d6be9fe"
 | 
					  resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.2.8.tgz#0b90f9382efda424c4f0f69a2ead6f6830d08ece"
 | 
				
			||||||
  integrity sha512-xpB8qYxgPuly166AGlpRjUdEYtmOWx2iCwGmrv4vqZL9YPVviDVPZPRXxnXr6xPZOdxQ9lp3ZBFCRgWJ7LE3Sg==
 | 
					  integrity sha512-1F0Xb2T21xET7oQV9eKuctbM9S7BC0fetoHCc4H13z0PT6haiRLP4T0ZY4XWh7iLP0usgqykT6p9B2RtOf4FPw==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    colorette "^1.2.1"
 | 
					    colorette "^1.2.2"
 | 
				
			||||||
    nanoid "^3.1.20"
 | 
					    nanoid "^3.1.20"
 | 
				
			||||||
    source-map "^0.6.1"
 | 
					    source-map "^0.6.1"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8998,10 +9010,10 @@ react-textarea-autosize@^8.3.2:
 | 
				
			||||||
    use-composed-ref "^1.0.0"
 | 
					    use-composed-ref "^1.0.0"
 | 
				
			||||||
    use-latest "^1.0.0"
 | 
					    use-latest "^1.0.0"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
react-toggle@^4.1.1:
 | 
					react-toggle@^4.1.2:
 | 
				
			||||||
  version "4.1.1"
 | 
					  version "4.1.2"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.1.tgz#2317f67bf918ea3508a96b09dd383efd9da572af"
 | 
					  resolved "https://registry.yarnpkg.com/react-toggle/-/react-toggle-4.1.2.tgz#b00500832f925ad524356d909821821ae39f6c52"
 | 
				
			||||||
  integrity sha512-+wXlMcSpg8SmnIXauMaZiKpR+r2wp2gMUteroejp2UTSqGTVvZLN+m9EhMzFARBKEw7KpQOwzCyfzeHeAndQGw==
 | 
					  integrity sha512-4Ohw31TuYQdhWfA6qlKafeXx3IOH7t4ZHhmRdwsm1fQREwOBGxJT+I22sgHqR/w8JRdk+AeMCJXPImEFSrNXow==
 | 
				
			||||||
  dependencies:
 | 
					  dependencies:
 | 
				
			||||||
    classnames "^2.2.5"
 | 
					    classnames "^2.2.5"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -10776,6 +10788,11 @@ type-fest@^0.11.0:
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
 | 
					  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.11.0.tgz#97abf0872310fed88a5c466b25681576145e33f1"
 | 
				
			||||||
  integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
 | 
					  integrity sha512-OdjXJxnCN1AvyLSzeKIgXTXxV+99ZuXl3Hpo9XpJAv9MBcHrrJOQ5kV7ypXOuQie+AmWG25hLbiKdwYTifzcfQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					type-fest@^0.20.2:
 | 
				
			||||||
 | 
					  version "0.20.2"
 | 
				
			||||||
 | 
					  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.20.2.tgz#1bf207f4b28f91583666cb5fbd327887301cd5f4"
 | 
				
			||||||
 | 
					  integrity sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==
 | 
				
			||||||
 | 
					
 | 
				
			||||||
type-fest@^0.6.0:
 | 
					type-fest@^0.6.0:
 | 
				
			||||||
  version "0.6.0"
 | 
					  version "0.6.0"
 | 
				
			||||||
  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
 | 
					  resolved "https://registry.yarnpkg.com/type-fest/-/type-fest-0.6.0.tgz#8d2a2370d3df886eb5c90ada1c5bf6188acf838b"
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in New Issue