Merge tag 'v2.6.0rc1' into instance_only_statuses

This commit is contained in:
Renato "Lond" Cerqueira
2018-10-23 08:32:55 +02:00
570 changed files with 11506 additions and 5693 deletions

View File

@ -223,11 +223,19 @@ class Account < ApplicationRecord
end
def fields_attributes=(attributes)
fields = []
fields = []
old_fields = self[:fields] || []
if attributes.is_a?(Hash)
attributes.each_value do |attr|
next if attr[:name].blank?
previous = old_fields.find { |item| item['value'] == attr[:value] }
if previous && previous['verified_at'].present?
attr[:verified_at] = previous['verified_at']
end
fields << attr
end
end
@ -235,13 +243,18 @@ class Account < ApplicationRecord
self[:fields] = fields
end
def build_fields
return if fields.size >= 4
DEFAULT_FIELDS_SIZE = 4
raw_fields = self[:fields] || []
add_fields = 4 - raw_fields.size
add_fields.times { raw_fields << { name: '', value: '' } }
self.fields = raw_fields
def build_fields
return if fields.size >= DEFAULT_FIELDS_SIZE
tmp = self[:fields] || []
(DEFAULT_FIELDS_SIZE - tmp.size).times do
tmp << { name: '', value: '' }
end
self.fields = tmp
end
def magic_key
@ -294,17 +307,52 @@ class Account < ApplicationRecord
end
class Field < ActiveModelSerializers::Model
attributes :name, :value, :account, :errors
attributes :name, :value, :verified_at, :account, :errors
def initialize(account, attr)
@account = account
@name = attr['name'].strip[0, 255]
@value = attr['value'].strip[0, 255]
@errors = {}
def initialize(account, attributes)
@account = account
@attributes = attributes
@name = attributes['name'].strip[0, string_limit]
@value = attributes['value'].strip[0, string_limit]
@verified_at = attributes['verified_at']&.to_datetime
@errors = {}
end
def verified?
verified_at.present?
end
def value_for_verification
@value_for_verification ||= begin
if account.local?
value
else
ActionController::Base.helpers.strip_tags(value)
end
end
end
def verifiable?
value_for_verification.present? && value_for_verification.start_with?('http://', 'https://')
end
def mark_verified!
@verified_at = Time.now.utc
@attributes['verified_at'] = @verified_at
end
def to_h
{ name: @name, value: @value }
{ name: @name, value: @value, verified_at: @verified_at }
end
private
def string_limit
if account.local?
255
else
2047
end
end
end

View File

@ -0,0 +1,113 @@
# frozen_string_literal: true
# == Schema Information
#
# Table name: account_conversations
#
# id :bigint(8) not null, primary key
# account_id :bigint(8)
# conversation_id :bigint(8)
# participant_account_ids :bigint(8) default([]), not null, is an Array
# status_ids :bigint(8) default([]), not null, is an Array
# last_status_id :bigint(8)
# lock_version :integer default(0), not null
# unread :boolean default(FALSE), not null
#
class AccountConversation < ApplicationRecord
after_commit :push_to_streaming_api
belongs_to :account
belongs_to :conversation
belongs_to :last_status, class_name: 'Status'
before_validation :set_last_status
def participant_account_ids=(arr)
self[:participant_account_ids] = arr.sort
end
def participant_accounts
if participant_account_ids.empty?
[account]
else
Account.where(id: participant_account_ids)
end
end
class << self
def paginate_by_id(limit, options = {})
if options[:min_id]
paginate_by_min_id(limit, options[:min_id]).reverse
else
paginate_by_max_id(limit, options[:max_id], options[:since_id])
end
end
def paginate_by_min_id(limit, min_id = nil)
query = order(arel_table[:last_status_id].asc).limit(limit)
query = query.where(arel_table[:last_status_id].gt(min_id)) if min_id.present?
query
end
def paginate_by_max_id(limit, max_id = nil, since_id = nil)
query = order(arel_table[:last_status_id].desc).limit(limit)
query = query.where(arel_table[:last_status_id].lt(max_id)) if max_id.present?
query = query.where(arel_table[:last_status_id].gt(since_id)) if since_id.present?
query
end
def add_status(recipient, status)
conversation = find_or_initialize_by(account: recipient, conversation_id: status.conversation_id, participant_account_ids: participants_from_status(recipient, status))
conversation.status_ids << status.id
conversation.unread = status.account_id != recipient.id
conversation.save
conversation
rescue ActiveRecord::StaleObjectError
retry
end
def remove_status(recipient, status)
conversation = find_by(account: recipient, conversation_id: status.conversation_id, participant_account_ids: participants_from_status(recipient, status))
return if conversation.nil?
conversation.status_ids.delete(status.id)
if conversation.status_ids.empty?
conversation.destroy
else
conversation.save
end
conversation
rescue ActiveRecord::StaleObjectError
retry
end
private
def participants_from_status(recipient, status)
((status.active_mentions.pluck(:account_id) + [status.account_id]).uniq - [recipient.id]).sort
end
end
private
def set_last_status
self.status_ids = status_ids.sort
self.last_status_id = status_ids.last
end
def push_to_streaming_api
return if destroyed? || !subscribed_to_timeline?
PushConversationWorker.perform_async(id)
end
def subscribed_to_timeline?
Redis.current.exists("subscribed:#{streaming_channel}")
end
def streaming_channel
"timeline:direct:#{account_id}"
end
end

View File

@ -8,7 +8,7 @@ class AccountFilter
end
def results
scope = Account.alphabetic
scope = Account.recent
params.each do |key, value|
scope.merge!(scope_for(key, value)) if value.present?
@ -29,8 +29,8 @@ class AccountFilter
Account.where(domain: value)
when 'silenced'
Account.silenced
when 'recent'
Account.recent
when 'alphabetic'
Account.reorder(nil).alphabetic
when 'suspended'
Account.suspended
when 'username'

View File

@ -26,7 +26,7 @@ module Omniauthable
# to prevent the identity being locked with accidentally created accounts.
# Note that this may leave zombie accounts (with no associated identity) which
# can be cleaned up at a later date.
user = signed_in_resource ? signed_in_resource : identity.user
user = signed_in_resource || identity.user
user = create_for_oauth(auth) if user.nil?
if identity.user.nil?
@ -61,7 +61,7 @@ module Omniauthable
display_name = auth.info.full_name || [auth.info.first_name, auth.info.last_name].join(' ')
{
email: email ? email : "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
email: email || "#{TEMP_EMAIL_PREFIX}-#{auth.uid}-#{auth.provider}.com",
password: Devise.friendly_token[0, 20],
account_attributes: {
username: ensure_unique_username(auth.uid),

View File

@ -19,5 +19,13 @@ module Paginable
query = query.where(arel_table[:id].gt(min_id)) if min_id.present?
query
}
scope :paginate_by_id, ->(limit, options = {}) {
if options[:min_id].present?
paginate_by_min_id(limit, options[:min_id]).reverse
else
paginate_by_max_id(limit, options[:max_id], options[:since_id])
end
}
end
end

View File

@ -3,12 +3,13 @@
#
# Table name: domain_blocks
#
# id :bigint(8) not null, primary key
# domain :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# severity :integer default("silence")
# reject_media :boolean default(FALSE), not null
# id :bigint(8) not null, primary key
# domain :string default(""), not null
# created_at :datetime not null
# updated_at :datetime not null
# severity :integer default("silence")
# reject_media :boolean default(FALSE), not null
# reject_reports :boolean default(FALSE), not null
#
class DomainBlock < ApplicationRecord

View File

@ -6,16 +6,20 @@ class Feed
@id = id
end
def get(limit, max_id = nil, since_id = nil)
from_redis(limit, max_id, since_id)
def get(limit, max_id = nil, since_id = nil, min_id = nil)
from_redis(limit, max_id, since_id, min_id)
end
protected
def from_redis(limit, max_id, since_id)
max_id = '+inf' if max_id.blank?
since_id = '-inf' if since_id.blank?
unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
def from_redis(limit, max_id, since_id, min_id)
if min_id.blank?
max_id = '+inf' if max_id.blank?
since_id = '-inf' if since_id.blank?
unhydrated = redis.zrevrangebyscore(key, "(#{max_id}", "(#{since_id}", limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
else
unhydrated = redis.zrangebyscore(key, "(#{min_id}", '+inf', limit: [0, limit], with_scores: true).map(&:first).map(&:to_i)
end
Status.where(id: unhydrated).cache_ids
end

View File

@ -25,6 +25,7 @@ class Follow < ApplicationRecord
has_one :notification, as: :activity, dependent: :destroy
validates :account_id, uniqueness: { scope: :target_account_id }
validates_with FollowLimitValidator, on: :create
scope :recent, -> { reorder(id: :desc) }

View File

@ -22,6 +22,7 @@ class FollowRequest < ApplicationRecord
has_one :notification, as: :activity, dependent: :destroy
validates :account_id, uniqueness: { scope: :target_account_id }
validates_with FollowLimitValidator, on: :create
def authorize!
account.follow!(target_account, reblogs: show_reblogs, uri: uri)

View File

@ -7,9 +7,9 @@ class HomeFeed < Feed
@account = account
end
def get(limit, max_id = nil, since_id = nil)
def get(limit, max_id = nil, since_id = nil, min_id = nil)
if redis.exists("account:#{@account.id}:regeneration")
from_database(limit, max_id, since_id)
from_database(limit, max_id, since_id, min_id)
else
super
end
@ -17,9 +17,9 @@ class HomeFeed < Feed
private
def from_database(limit, max_id, since_id)
def from_database(limit, max_id, since_id, min_id)
Status.as_home_timeline(@account)
.paginate_by_max_id(limit, max_id, since_id)
.paginate_by_id(limit, max_id: max_id, since_id: since_id, min_id: min_id)
.reject { |status| FeedManager.instance.filter?(:home, status, @account.id) }
end
end

View File

@ -8,6 +8,7 @@
# created_at :datetime not null
# updated_at :datetime not null
# account_id :bigint(8)
# silent :boolean default(FALSE), not null
#
class Mention < ApplicationRecord
@ -18,10 +19,17 @@ class Mention < ApplicationRecord
validates :account, uniqueness: { scope: :status }
scope :active, -> { where(silent: false) }
scope :silent, -> { where(silent: true) }
delegate(
:username,
:acct,
to: :account,
prefix: true
)
def active?
!silent?
end
end

View File

@ -24,7 +24,7 @@ class Notification < ApplicationRecord
favourite: 'Favourite',
}.freeze
STATUS_INCLUDES = [:account, :application, :stream_entry, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :application, :media_attachments, :tags, mentions: :account]].freeze
STATUS_INCLUDES = [:account, :application, :media_attachments, :tags, active_mentions: :account, reblog: [:account, :application, :media_attachments, :tags, active_mentions: :account]].freeze
belongs_to :account, optional: true
belongs_to :from_account, class_name: 'Account', optional: true

View File

@ -25,6 +25,8 @@
#
class Status < ApplicationRecord
before_destroy :unlink_from_conversations
include Paginable
include Streamable
include Cacheable
@ -36,7 +38,7 @@ class Status < ApplicationRecord
update_index('statuses#status', :proper) if Chewy.enabled?
enum visibility: [:public, :unlisted, :private, :direct], _suffix: :visibility
enum visibility: [:public, :unlisted, :private, :direct, :limited], _suffix: :visibility
belongs_to :application, class_name: 'Doorkeeper::Application', optional: true
@ -50,7 +52,8 @@ class Status < ApplicationRecord
has_many :favourites, inverse_of: :status, dependent: :destroy
has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status', inverse_of: :reblog, dependent: :destroy
has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status', inverse_of: :thread
has_many :mentions, dependent: :destroy
has_many :mentions, dependent: :destroy, inverse_of: :status
has_many :active_mentions, -> { active }, class_name: 'Mention', inverse_of: :status
has_many :media_attachments, dependent: :nullify
has_and_belongs_to_many :tags
@ -89,7 +92,7 @@ class Status < ApplicationRecord
:status_stat,
:tags,
:stream_entry,
mentions: :account,
active_mentions: :account,
reblog: [
:account,
:application,
@ -98,7 +101,7 @@ class Status < ApplicationRecord
:media_attachments,
:conversation,
:status_stat,
mentions: :account,
active_mentions: :account,
],
thread: :account
@ -175,7 +178,11 @@ class Status < ApplicationRecord
end
def hidden?
private_visibility? || direct_visibility?
private_visibility? || direct_visibility? || limited_visibility?
end
def distributable?
public_visibility? || unlisted_visibility?
end
def with_media?
@ -239,6 +246,10 @@ class Status < ApplicationRecord
left_outer_joins(:status_stat).select('statuses.id, greatest(statuses.updated_at, status_stats.updated_at) AS updated_at')
end
def selectable_visibilities
visibilities.keys - %w(direct limited)
end
def in_chosen_languages(account)
where(language: nil).or where(language: account.chosen_languages)
end
@ -249,7 +260,7 @@ class Status < ApplicationRecord
def as_direct_timeline(account, limit = 20, max_id = nil, since_id = nil, cache_ids = false)
# direct timeline is mix of direct message from_me and to_me.
# 2 querys are executed with pagination.
# 2 queries are executed with pagination.
# constant expression using arel_table is required for partial index
# _from_me part does not require any timeline filters
@ -485,4 +496,15 @@ class Status < ApplicationRecord
reblog&.decrement_count!(:reblogs_count) if reblog?
thread&.decrement_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
end
def unlink_from_conversations
return unless direct_visibility?
mentioned_accounts = mentions.includes(:account).map(&:account)
inbox_owners = mentioned_accounts.select(&:local?) + (account.local? ? [account] : [])
inbox_owners.each do |inbox_owner|
AccountConversation.remove_status(inbox_owner, self)
end
end
end

View File

@ -49,7 +49,7 @@ class StreamEntry < ApplicationRecord
end
def mentions
orphaned? ? [] : status.mentions.map(&:account)
orphaned? ? [] : status.active_mentions.map(&:account)
end
private

View File

@ -95,8 +95,8 @@ class User < ApplicationRecord
has_many :session_activations, dependent: :destroy
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
:reduce_motion, :system_font_ui, :noindex, :theme, :display_sensitive_media, :hide_network,
:default_language, :default_federation, to: :settings, prefix: :setting, allow_nil: false
:reduce_motion, :system_font_ui, :noindex, :theme, :display_media, :hide_network,
:expand_spoilers, :default_language, :default_federation, to: :settings, prefix: :setting, allow_nil: false
attr_reader :invite_code
@ -316,6 +316,14 @@ class User < ApplicationRecord
super
end
def show_all_media?
setting_display_media == 'show_all'
end
def hide_all_media?
setting_display_media == 'hide_all'
end
protected
def send_devise_notification(notification, *args)