Merge remote-tracking branch 'tootsuite/master' into glitchsoc/master
This commit is contained in:
		
						commit
						e768ef35b1
					
				
					 25 changed files with 193 additions and 60 deletions
				
			
		| 
						 | 
					@ -19,7 +19,6 @@ env:
 | 
				
			||||||
    - LOCAL_HTTPS=true
 | 
					    - LOCAL_HTTPS=true
 | 
				
			||||||
    - RAILS_ENV=test
 | 
					    - RAILS_ENV=test
 | 
				
			||||||
    - PARALLEL_TEST_PROCESSORS=2
 | 
					    - PARALLEL_TEST_PROCESSORS=2
 | 
				
			||||||
    - "PATH=$HOME:$PATH"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
addons:
 | 
					addons:
 | 
				
			||||||
  postgresql: 9.4
 | 
					  postgresql: 9.4
 | 
				
			||||||
| 
						 | 
					@ -49,7 +48,6 @@ install:
 | 
				
			||||||
 | 
					
 | 
				
			||||||
before_script:
 | 
					before_script:
 | 
				
			||||||
  - ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
 | 
					  - ./bin/rails parallel:create parallel:load_schema parallel:prepare assets:precompile
 | 
				
			||||||
  - ln -s /usr/bin/x86_64-linux-gnu-g++-6 "$HOME/g++"
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
script:
 | 
					script:
 | 
				
			||||||
  - travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
 | 
					  - travis_retry bundle exec parallel_test spec/ --group-by filesize --type rspec
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -20,6 +20,6 @@ class Api::V1::Accounts::CredentialsController < Api::BaseController
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def account_params
 | 
					  def account_params
 | 
				
			||||||
    params.permit(:display_name, :note, :avatar, :header)
 | 
					    params.permit(:display_name, :note, :avatar, :header, :locked)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,5 @@
 | 
				
			||||||
import api from '../api';
 | 
					import api from '../api';
 | 
				
			||||||
 | 
					import { fetchRelationships } from './accounts';
 | 
				
			||||||
 | 
					
 | 
				
			||||||
export const SEARCH_CHANGE = 'SEARCH_CHANGE';
 | 
					export const SEARCH_CHANGE = 'SEARCH_CHANGE';
 | 
				
			||||||
export const SEARCH_CLEAR  = 'SEARCH_CLEAR';
 | 
					export const SEARCH_CLEAR  = 'SEARCH_CLEAR';
 | 
				
			||||||
| 
						 | 
					@ -38,6 +39,7 @@ export function submitSearch() {
 | 
				
			||||||
      },
 | 
					      },
 | 
				
			||||||
    }).then(response => {
 | 
					    }).then(response => {
 | 
				
			||||||
      dispatch(fetchSearchSuccess(response.data));
 | 
					      dispatch(fetchSearchSuccess(response.data));
 | 
				
			||||||
 | 
					      dispatch(fetchRelationships(response.data.accounts.map(item => item.id)));
 | 
				
			||||||
    }).catch(error => {
 | 
					    }).catch(error => {
 | 
				
			||||||
      dispatch(fetchSearchFail(error));
 | 
					      dispatch(fetchSearchFail(error));
 | 
				
			||||||
    });
 | 
					    });
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -227,12 +227,8 @@ export default class MediaGallery extends React.PureComponent {
 | 
				
			||||||
    const style = {};
 | 
					    const style = {};
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    if (this.isStandaloneEligible()) {
 | 
					    if (this.isStandaloneEligible()) {
 | 
				
			||||||
      if (!visible && width) {
 | 
					      if (width) {
 | 
				
			||||||
        // only need to forcibly set the height in "sensitive" mode
 | 
					 | 
				
			||||||
        style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
 | 
					        style.height = width / this.props.media.getIn([0, 'meta', 'small', 'aspect']);
 | 
				
			||||||
      } else {
 | 
					 | 
				
			||||||
        // layout automatically, using image's natural aspect ratio
 | 
					 | 
				
			||||||
        style.height = '';
 | 
					 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    } else {
 | 
					    } else {
 | 
				
			||||||
      // crop the image
 | 
					      // crop the image
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -122,5 +122,6 @@ button {
 | 
				
			||||||
    height: 100%;
 | 
					    height: 100%;
 | 
				
			||||||
    align-items: center;
 | 
					    align-items: center;
 | 
				
			||||||
    justify-content: center;
 | 
					    justify-content: center;
 | 
				
			||||||
 | 
					    outline: 0 !important;
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -686,12 +686,13 @@
 | 
				
			||||||
  background: transparent;
 | 
					  background: transparent;
 | 
				
			||||||
  border: 0;
 | 
					  border: 0;
 | 
				
			||||||
  color: lighten($ui-base-color, 8%);
 | 
					  color: lighten($ui-base-color, 8%);
 | 
				
			||||||
  font-weight: 500;
 | 
					  font-weight: 700;
 | 
				
			||||||
  font-size: 11px;
 | 
					  font-size: 11px;
 | 
				
			||||||
  padding: 0 6px;
 | 
					  padding: 0 6px;
 | 
				
			||||||
  text-transform: uppercase;
 | 
					  text-transform: uppercase;
 | 
				
			||||||
  line-height: inherit;
 | 
					  line-height: 20px;
 | 
				
			||||||
  cursor: pointer;
 | 
					  cursor: pointer;
 | 
				
			||||||
 | 
					  vertical-align: middle;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.status__prepend-icon-wrapper {
 | 
					.status__prepend-icon-wrapper {
 | 
				
			||||||
| 
						 | 
					@ -899,6 +900,11 @@
 | 
				
			||||||
      height: 24px;
 | 
					      height: 24px;
 | 
				
			||||||
      margin: -1px 0 0;
 | 
					      margin: -1px 0 0;
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    .status__content__spoiler-link {
 | 
				
			||||||
 | 
					      line-height: 24px;
 | 
				
			||||||
 | 
					      margin: -1px 0 0;
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .video-player {
 | 
					  .video-player {
 | 
				
			||||||
| 
						 | 
					@ -2667,12 +2673,16 @@ a.status-card {
 | 
				
			||||||
  background: $base-overlay-background;
 | 
					  background: $base-overlay-background;
 | 
				
			||||||
  color: $ui-primary-color;
 | 
					  color: $ui-primary-color;
 | 
				
			||||||
  border: 0;
 | 
					  border: 0;
 | 
				
			||||||
 | 
					  padding: 0;
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
  height: 100%;
 | 
					  height: 100%;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					  appearance: none;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &:hover,
 | 
					  &:hover,
 | 
				
			||||||
  &:active,
 | 
					  &:active,
 | 
				
			||||||
  &:focus {
 | 
					  &:focus {
 | 
				
			||||||
 | 
					    padding: 0;
 | 
				
			||||||
    color: lighten($ui-primary-color, 8%);
 | 
					    color: lighten($ui-primary-color, 8%);
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -2685,7 +2695,7 @@ a.status-card {
 | 
				
			||||||
.media-spoiler__trigger {
 | 
					.media-spoiler__trigger {
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  font-size: 11px;
 | 
					  font-size: 11px;
 | 
				
			||||||
  font-weight: 500;
 | 
					  font-weight: 700;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
.spoiler-button {
 | 
					.spoiler-button {
 | 
				
			||||||
| 
						 | 
					@ -4091,6 +4101,7 @@ a.status-card {
 | 
				
			||||||
  box-sizing: border-box;
 | 
					  box-sizing: border-box;
 | 
				
			||||||
  margin-top: 8px;
 | 
					  margin-top: 8px;
 | 
				
			||||||
  overflow: hidden;
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
  position: relative;
 | 
					  position: relative;
 | 
				
			||||||
  width: 100%;
 | 
					  width: 100%;
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
| 
						 | 
					@ -4101,6 +4112,8 @@ a.status-card {
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  float: left;
 | 
					  float: left;
 | 
				
			||||||
  position: relative;
 | 
					  position: relative;
 | 
				
			||||||
 | 
					  border-radius: 4px;
 | 
				
			||||||
 | 
					  overflow: hidden;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  &.standalone {
 | 
					  &.standalone {
 | 
				
			||||||
    .media-gallery__item-gifv-thumbnail {
 | 
					    .media-gallery__item-gifv-thumbnail {
 | 
				
			||||||
| 
						 | 
					@ -4113,6 +4126,7 @@ a.status-card {
 | 
				
			||||||
  cursor: zoom-in;
 | 
					  cursor: zoom-in;
 | 
				
			||||||
  display: block;
 | 
					  display: block;
 | 
				
			||||||
  text-decoration: none;
 | 
					  text-decoration: none;
 | 
				
			||||||
 | 
					  color: $ui-secondary-color;
 | 
				
			||||||
  height: 100%;
 | 
					  height: 100%;
 | 
				
			||||||
  line-height: 0;
 | 
					  line-height: 0;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -146,10 +146,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      a.status__content__spoiler-link {
 | 
					      a.status__content__spoiler-link {
 | 
				
			||||||
        color: $primary-text-color;
 | 
					        color: $primary-text-color;
 | 
				
			||||||
        background: $ui-primary-color;
 | 
					        background: $ui-base-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        &:hover {
 | 
					        &:hover {
 | 
				
			||||||
          background: lighten($ui-primary-color, 8%);
 | 
					          background: lighten($ui-base-color, 8%);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -214,10 +214,10 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      a.status__content__spoiler-link {
 | 
					      a.status__content__spoiler-link {
 | 
				
			||||||
        color: $primary-text-color;
 | 
					        color: $primary-text-color;
 | 
				
			||||||
        background: $ui-primary-color;
 | 
					        background: $ui-base-color;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        &:hover {
 | 
					        &:hover {
 | 
				
			||||||
          background: lighten($ui-primary-color, 8%);
 | 
					          background: lighten($ui-base-color, 8%);
 | 
				
			||||||
        }
 | 
					        }
 | 
				
			||||||
      }
 | 
					      }
 | 
				
			||||||
    }
 | 
					    }
 | 
				
			||||||
| 
						 | 
					@ -260,16 +260,8 @@
 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .media-spoiler {
 | 
					  .media-spoiler {
 | 
				
			||||||
    background: $ui-primary-color;
 | 
					    background: $ui-base-color;
 | 
				
			||||||
    color: $white;
 | 
					    color: $ui-primary-color;
 | 
				
			||||||
    transition: all 40ms linear;
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
    &:hover,
 | 
					 | 
				
			||||||
    &:active,
 | 
					 | 
				
			||||||
    &:focus {
 | 
					 | 
				
			||||||
      background: darken($ui-primary-color, 2%);
 | 
					 | 
				
			||||||
      color: unset;
 | 
					 | 
				
			||||||
    }
 | 
					 | 
				
			||||||
  }
 | 
					  }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  .pre-header {
 | 
					  .pre-header {
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -74,7 +74,7 @@ class ActivityPub::Activity
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    # Only continue if the status is supposed to have
 | 
					    # Only continue if the status is supposed to have
 | 
				
			||||||
    # arrived in real-time
 | 
					    # arrived in real-time
 | 
				
			||||||
    return unless @options[:override_timestamps]
 | 
					    return unless @options[:override_timestamps] || status.within_realtime_window?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    distribute_to_followers(status)
 | 
					    distribute_to_followers(status)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -61,7 +61,7 @@ class OStatus::Activity::Creation < OStatus::Activity::Base
 | 
				
			||||||
    Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution"
 | 
					    Rails.logger.debug "Queuing remote status #{status.id} (#{id}) for distribution"
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
 | 
					    LinkCrawlWorker.perform_async(status.id) unless status.spoiler_text?
 | 
				
			||||||
    DistributionWorker.perform_async(status.id) if @options[:override_timestamps]
 | 
					    DistributionWorker.perform_async(status.id) if @options[:override_timestamps] || status.within_realtime_window?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    status
 | 
					    status
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -16,12 +16,16 @@ class AccountDomainBlock < ApplicationRecord
 | 
				
			||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  validates :domain, presence: true, uniqueness: { scope: :account_id }
 | 
					  validates :domain, presence: true, uniqueness: { scope: :account_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  after_create  :remove_blocking_cache
 | 
					  after_commit :remove_blocking_cache
 | 
				
			||||||
  after_destroy :remove_blocking_cache
 | 
					  after_commit :remove_relationship_cache
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def remove_blocking_cache
 | 
					  def remove_blocking_cache
 | 
				
			||||||
    Rails.cache.delete("exclude_domains_for:#{account_id}")
 | 
					    Rails.cache.delete("exclude_domains_for:#{account_id}")
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def remove_relationship_cache
 | 
				
			||||||
 | 
					    Rails.cache.delete_matched("relationship:#{account_id}:*")
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -12,14 +12,14 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Block < ApplicationRecord
 | 
					class Block < ApplicationRecord
 | 
				
			||||||
  include Paginable
 | 
					  include Paginable
 | 
				
			||||||
 | 
					  include RelationshipCacheable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  belongs_to :target_account, class_name: 'Account'
 | 
					  belongs_to :target_account, class_name: 'Account'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
					  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  after_create  :remove_blocking_cache
 | 
					  after_commit :remove_blocking_cache
 | 
				
			||||||
  after_destroy :remove_blocking_cache
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,9 +7,15 @@ module AccountAvatar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  class_methods do
 | 
					  class_methods do
 | 
				
			||||||
    def avatar_styles(file)
 | 
					    def avatar_styles(file)
 | 
				
			||||||
      styles = { original: '120x120#' }
 | 
					      styles   = {}
 | 
				
			||||||
 | 
					      geometry = Paperclip::Geometry.from_file(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      styles[:original] = '120x120#' if geometry.width != geometry.height || geometry.width > 120 || geometry.height > 120
 | 
				
			||||||
      styles[:static]   = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
 | 
					      styles[:static]   = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      styles
 | 
					      styles
 | 
				
			||||||
 | 
					    rescue Paperclip::Errors::NotIdentifiedByImageMagickError
 | 
				
			||||||
 | 
					      {}
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private :avatar_styles
 | 
					    private :avatar_styles
 | 
				
			||||||
| 
						 | 
					@ -17,7 +23,7 @@ module AccountAvatar
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  included do
 | 
					  included do
 | 
				
			||||||
    # Avatar upload
 | 
					    # Avatar upload
 | 
				
			||||||
    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-quality 80 -strip' }
 | 
					    has_attached_file :avatar, styles: ->(f) { avatar_styles(f) }, convert_options: { all: '-strip' }
 | 
				
			||||||
    validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
 | 
					    validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
 | 
				
			||||||
    validates_attachment_size :avatar, less_than: 2.megabytes
 | 
					    validates_attachment_size :avatar, less_than: 2.megabytes
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -7,9 +7,15 @@ module AccountHeader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  class_methods do
 | 
					  class_methods do
 | 
				
			||||||
    def header_styles(file)
 | 
					    def header_styles(file)
 | 
				
			||||||
      styles = { original: '700x335#' }
 | 
					      styles   = {}
 | 
				
			||||||
 | 
					      geometry = Paperclip::Geometry.from_file(file)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      styles[:original] = '700x335#' unless geometry.width == 700 && geometry.height == 335
 | 
				
			||||||
      styles[:static]   = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
 | 
					      styles[:static]   = { format: 'png', convert_options: '-coalesce' } if file.content_type == 'image/gif'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      styles
 | 
					      styles
 | 
				
			||||||
 | 
					    rescue Paperclip::Errors::NotIdentifiedByImageMagickError
 | 
				
			||||||
 | 
					      {}
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    private :header_styles
 | 
					    private :header_styles
 | 
				
			||||||
| 
						 | 
					@ -17,7 +23,7 @@ module AccountHeader
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  included do
 | 
					  included do
 | 
				
			||||||
    # Header upload
 | 
					    # Header upload
 | 
				
			||||||
    has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-quality 80 -strip' }
 | 
					    has_attached_file :header, styles: ->(f) { header_styles(f) }, convert_options: { all: '-strip' }
 | 
				
			||||||
    validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
 | 
					    validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
 | 
				
			||||||
    validates_attachment_size :header, less_than: 2.megabytes
 | 
					    validates_attachment_size :header, less_than: 2.megabytes
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										16
									
								
								app/models/concerns/relationship_cacheable.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/models/concerns/relationship_cacheable.rb
									
									
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					# frozen_string_literal: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					module RelationshipCacheable
 | 
				
			||||||
 | 
					  extend ActiveSupport::Concern
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  included do
 | 
				
			||||||
 | 
					    after_commit :remove_relationship_cache
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def remove_relationship_cache
 | 
				
			||||||
 | 
					    Rails.cache.delete("relationship:#{account_id}:#{target_account_id}")
 | 
				
			||||||
 | 
					    Rails.cache.delete("relationship:#{target_account_id}:#{account_id}")
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Follow < ApplicationRecord
 | 
					class Follow < ApplicationRecord
 | 
				
			||||||
  include Paginable
 | 
					  include Paginable
 | 
				
			||||||
 | 
					  include RelationshipCacheable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :account, counter_cache: :following_count
 | 
					  belongs_to :account, counter_cache: :following_count
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,6 +13,7 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class FollowRequest < ApplicationRecord
 | 
					class FollowRequest < ApplicationRecord
 | 
				
			||||||
  include Paginable
 | 
					  include Paginable
 | 
				
			||||||
 | 
					  include RelationshipCacheable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  belongs_to :target_account, class_name: 'Account'
 | 
					  belongs_to :target_account, class_name: 'Account'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -13,14 +13,14 @@
 | 
				
			||||||
 | 
					
 | 
				
			||||||
class Mute < ApplicationRecord
 | 
					class Mute < ApplicationRecord
 | 
				
			||||||
  include Paginable
 | 
					  include Paginable
 | 
				
			||||||
 | 
					  include RelationshipCacheable
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  belongs_to :target_account, class_name: 'Account'
 | 
					  belongs_to :target_account, class_name: 'Account'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
					  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  after_create  :remove_blocking_cache
 | 
					  after_commit :remove_blocking_cache
 | 
				
			||||||
  after_destroy :remove_blocking_cache
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -83,6 +83,8 @@ class Status < ApplicationRecord
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  delegate :domain, to: :account, prefix: true
 | 
					  delegate :domain, to: :account, prefix: true
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  REAL_TIME_WINDOW = 6.hours
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def searchable_by(preloaded = nil)
 | 
					  def searchable_by(preloaded = nil)
 | 
				
			||||||
    ids = [account_id]
 | 
					    ids = [account_id]
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -111,6 +113,10 @@ class Status < ApplicationRecord
 | 
				
			||||||
    !reblog_of_id.nil?
 | 
					    !reblog_of_id.nil?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def within_realtime_window?
 | 
				
			||||||
 | 
					    created_at >= REAL_TIME_WINDOW.ago
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def verb
 | 
					  def verb
 | 
				
			||||||
    if destroyed?
 | 
					    if destroyed?
 | 
				
			||||||
      :delete
 | 
					      :delete
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -5,11 +5,67 @@ class AccountRelationshipsPresenter
 | 
				
			||||||
              :muting, :requested, :domain_blocking
 | 
					              :muting, :requested, :domain_blocking
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def initialize(account_ids, current_account_id, **options)
 | 
					  def initialize(account_ids, current_account_id, **options)
 | 
				
			||||||
    @following       = Account.following_map(account_ids, current_account_id).merge(options[:following_map] || {})
 | 
					    @account_ids        = account_ids.map { |a| a.is_a?(Account) ? a.id : a }
 | 
				
			||||||
    @followed_by     = Account.followed_by_map(account_ids, current_account_id).merge(options[:followed_by_map] || {})
 | 
					    @current_account_id = current_account_id
 | 
				
			||||||
    @blocking        = Account.blocking_map(account_ids, current_account_id).merge(options[:blocking_map] || {})
 | 
					
 | 
				
			||||||
    @muting          = Account.muting_map(account_ids, current_account_id).merge(options[:muting_map] || {})
 | 
					    @following       = cached[:following].merge(Account.following_map(@uncached_account_ids, @current_account_id))
 | 
				
			||||||
    @requested       = Account.requested_map(account_ids, current_account_id).merge(options[:requested_map] || {})
 | 
					    @followed_by     = cached[:followed_by].merge(Account.followed_by_map(@uncached_account_ids, @current_account_id))
 | 
				
			||||||
    @domain_blocking = Account.domain_blocking_map(account_ids, current_account_id).merge(options[:domain_blocking_map] || {})
 | 
					    @blocking        = cached[:blocking].merge(Account.blocking_map(@uncached_account_ids, @current_account_id))
 | 
				
			||||||
 | 
					    @muting          = cached[:muting].merge(Account.muting_map(@uncached_account_ids, @current_account_id))
 | 
				
			||||||
 | 
					    @requested       = cached[:requested].merge(Account.requested_map(@uncached_account_ids, @current_account_id))
 | 
				
			||||||
 | 
					    @domain_blocking = cached[:domain_blocking].merge(Account.domain_blocking_map(@uncached_account_ids, @current_account_id))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    cache_uncached!
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @following.merge!(options[:following_map] || {})
 | 
				
			||||||
 | 
					    @followed_by.merge!(options[:followed_by_map] || {})
 | 
				
			||||||
 | 
					    @blocking.merge!(options[:blocking_map] || {})
 | 
				
			||||||
 | 
					    @muting.merge!(options[:muting_map] || {})
 | 
				
			||||||
 | 
					    @requested.merge!(options[:requested_map] || {})
 | 
				
			||||||
 | 
					    @domain_blocking.merge!(options[:domain_blocking_map] || {})
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def cached
 | 
				
			||||||
 | 
					    return @cached if defined?(@cached)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @cached = {
 | 
				
			||||||
 | 
					      following: {},
 | 
				
			||||||
 | 
					      followed_by: {},
 | 
				
			||||||
 | 
					      blocking: {},
 | 
				
			||||||
 | 
					      muting: {},
 | 
				
			||||||
 | 
					      requested: {},
 | 
				
			||||||
 | 
					      domain_blocking: {},
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @uncached_account_ids = []
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @account_ids.each do |account_id|
 | 
				
			||||||
 | 
					      maps_for_account = Rails.cache.read("relationship:#{@current_account_id}:#{account_id}")
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      if maps_for_account.is_a?(Hash)
 | 
				
			||||||
 | 
					        @cached.merge!(maps_for_account)
 | 
				
			||||||
 | 
					      else
 | 
				
			||||||
 | 
					        @uncached_account_ids << account_id
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    @cached
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def cache_uncached!
 | 
				
			||||||
 | 
					    @uncached_account_ids.each do |account_id|
 | 
				
			||||||
 | 
					      maps_for_account = {
 | 
				
			||||||
 | 
					        following:       { account_id => following[account_id] },
 | 
				
			||||||
 | 
					        followed_by:     { account_id => followed_by[account_id] },
 | 
				
			||||||
 | 
					        blocking:        { account_id => blocking[account_id] },
 | 
				
			||||||
 | 
					        muting:          { account_id => muting[account_id] },
 | 
				
			||||||
 | 
					        requested:       { account_id => requested[account_id] },
 | 
				
			||||||
 | 
					        domain_blocking: { account_id => domain_blocking[account_id] },
 | 
				
			||||||
 | 
					      }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      Rails.cache.write("relationship:#{@current_account_id}:#{account_id}", maps_for_account, expires_in: 1.day)
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -23,6 +23,10 @@ Rails.application.configure do
 | 
				
			||||||
  config.consider_all_requests_local       = true
 | 
					  config.consider_all_requests_local       = true
 | 
				
			||||||
  config.action_controller.perform_caching = false
 | 
					  config.action_controller.perform_caching = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # The default store, file_store is shared by processses parallely executed
 | 
				
			||||||
 | 
					  # and should not be used.
 | 
				
			||||||
 | 
					  config.cache_store = :memory_store
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  # Raise exceptions instead of rendering exception templates.
 | 
					  # Raise exceptions instead of rendering exception templates.
 | 
				
			||||||
  config.action_dispatch.show_exceptions = false
 | 
					  config.action_dispatch.show_exceptions = false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -8,7 +8,7 @@ RSpec.describe NotificationMailer, type: :mailer do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  shared_examples 'localized subject' do |*args, **kwrest|
 | 
					  shared_examples 'localized subject' do |*args, **kwrest|
 | 
				
			||||||
    it 'renders subject localized for the locale of the receiver' do
 | 
					    it 'renders subject localized for the locale of the receiver' do
 | 
				
			||||||
      locale = I18n.available_locales.sample
 | 
					      locale = %i(de en).sample
 | 
				
			||||||
      receiver.update!(locale: locale)
 | 
					      receiver.update!(locale: locale)
 | 
				
			||||||
      expect(mail.subject).to eq I18n.t(*args, kwrest.merge(locale: locale))
 | 
					      expect(mail.subject).to eq I18n.t(*args, kwrest.merge(locale: locale))
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -42,11 +42,6 @@ RSpec.describe Setting, type: :model do
 | 
				
			||||||
        described_class[key]
 | 
					        described_class[key]
 | 
				
			||||||
      end
 | 
					      end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
      it 'calls Rails.cache.fetch' do
 | 
					 | 
				
			||||||
        expect(Rails).to receive_message_chain(:cache, :fetch).with(cache_key)
 | 
					 | 
				
			||||||
        described_class[key]
 | 
					 | 
				
			||||||
      end
 | 
					 | 
				
			||||||
 | 
					 | 
				
			||||||
      context 'Rails.cache does not exists' do
 | 
					      context 'Rails.cache does not exists' do
 | 
				
			||||||
        before do
 | 
					        before do
 | 
				
			||||||
          allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
 | 
					          allow(RailsSettings::Settings).to receive(:object).with(key).and_return(object)
 | 
				
			||||||
| 
						 | 
					@ -103,6 +98,14 @@ RSpec.describe Setting, type: :model do
 | 
				
			||||||
          Rails.cache.write(cache_key, cache_value)
 | 
					          Rails.cache.write(cache_key, cache_value)
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        it 'does not query the database' do
 | 
				
			||||||
 | 
					          expect do |callback|
 | 
				
			||||||
 | 
					            ActiveSupport::Notifications.subscribed callback, 'sql.active_record' do
 | 
				
			||||||
 | 
					              described_class[key]
 | 
				
			||||||
 | 
					            end
 | 
				
			||||||
 | 
					          end.not_to yield_control
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
        it 'returns the cached value' do
 | 
					        it 'returns the cached value' do
 | 
				
			||||||
          expect(described_class[key]).to eq cache_value
 | 
					          expect(described_class[key]).to eq cache_value
 | 
				
			||||||
        end
 | 
					        end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -90,9 +90,7 @@ describe InstancePresenter do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe "user_count" do
 | 
					  describe "user_count" do
 | 
				
			||||||
    it "returns the number of site users" do
 | 
					    it "returns the number of site users" do
 | 
				
			||||||
      cache = double
 | 
					      Rails.cache.write 'user_count', 123
 | 
				
			||||||
      allow(Rails).to receive(:cache).and_return(cache)
 | 
					 | 
				
			||||||
      allow(cache).to receive(:fetch).with("user_count").and_return(123)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(instance_presenter.user_count).to eq(123)
 | 
					      expect(instance_presenter.user_count).to eq(123)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -100,9 +98,7 @@ describe InstancePresenter do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe "status_count" do
 | 
					  describe "status_count" do
 | 
				
			||||||
    it "returns the number of local statuses" do
 | 
					    it "returns the number of local statuses" do
 | 
				
			||||||
      cache = double
 | 
					      Rails.cache.write 'local_status_count', 234
 | 
				
			||||||
      allow(Rails).to receive(:cache).and_return(cache)
 | 
					 | 
				
			||||||
      allow(cache).to receive(:fetch).with("local_status_count").and_return(234)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(instance_presenter.status_count).to eq(234)
 | 
					      expect(instance_presenter.status_count).to eq(234)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					@ -110,9 +106,7 @@ describe InstancePresenter do
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  describe "domain_count" do
 | 
					  describe "domain_count" do
 | 
				
			||||||
    it "returns the number of known domains" do
 | 
					    it "returns the number of known domains" do
 | 
				
			||||||
      cache = double
 | 
					      Rails.cache.write 'distinct_domain_count', 345
 | 
				
			||||||
      allow(Rails).to receive(:cache).and_return(cache)
 | 
					 | 
				
			||||||
      allow(cache).to receive(:fetch).with("distinct_domain_count").and_return(345)
 | 
					 | 
				
			||||||
 | 
					
 | 
				
			||||||
      expect(instance_presenter.domain_count).to eq(345)
 | 
					      expect(instance_presenter.domain_count).to eq(345)
 | 
				
			||||||
    end
 | 
					    end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -51,6 +51,8 @@ RSpec.configure do |config|
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  config.after :each do
 | 
					  config.after :each do
 | 
				
			||||||
 | 
					    Rails.cache.clear
 | 
				
			||||||
 | 
					
 | 
				
			||||||
    keys = Redis.current.keys
 | 
					    keys = Redis.current.keys
 | 
				
			||||||
    Redis.current.del(keys) if keys.any?
 | 
					    Redis.current.del(keys) if keys.any?
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,35 @@
 | 
				
			||||||
require 'rails_helper'
 | 
					require 'rails_helper'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
RSpec.describe FetchRemoteStatusService do
 | 
					RSpec.describe FetchRemoteStatusService do
 | 
				
			||||||
 | 
					  let(:account) { Fabricate(:account) }
 | 
				
			||||||
 | 
					  let(:prefetched_body) { nil }
 | 
				
			||||||
 | 
					  let(:valid_domain) { Rails.configuration.x.local_domain }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  let(:note) do
 | 
				
			||||||
 | 
					    {
 | 
				
			||||||
 | 
					      '@context': 'https://www.w3.org/ns/activitystreams',
 | 
				
			||||||
 | 
					      id: "https://#{valid_domain}/@foo/1234",
 | 
				
			||||||
 | 
					      type: 'Note',
 | 
				
			||||||
 | 
					      content: 'Lorem ipsum',
 | 
				
			||||||
 | 
					      attributedTo: ActivityPub::TagManager.instance.uri_for(account),
 | 
				
			||||||
 | 
					    }
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  context 'protocol is :activitypub' do
 | 
				
			||||||
 | 
					    subject { described_class.new.call(note[:id], prefetched_body, protocol) }
 | 
				
			||||||
 | 
					    let(:prefetched_body) { Oj.dump(note) }
 | 
				
			||||||
 | 
					    let(:protocol) { :activitypub }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    before do
 | 
				
			||||||
 | 
					      account.update(uri: ActivityPub::TagManager.instance.uri_for(account))
 | 
				
			||||||
 | 
					      subject
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    it 'creates status' do
 | 
				
			||||||
 | 
					      status = account.statuses.first
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					      expect(status).to_not be_nil
 | 
				
			||||||
 | 
					      expect(status.text).to eq 'Lorem ipsum'
 | 
				
			||||||
 | 
					    end
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
		Loading…
	
		Reference in a new issue