Commiting
This commit is contained in:
@@ -35,7 +35,7 @@
|
||||
# outbox_url :string default(""), not null
|
||||
# shared_inbox_url :string default(""), not null
|
||||
# followers_url :string default(""), not null
|
||||
# protocol :integer default("ostatus"), not null
|
||||
# protocol :integer default(0), not null
|
||||
# memorial :boolean default(FALSE), not null
|
||||
# moved_to_account_id :bigint(8)
|
||||
# featured_collection_url :string
|
||||
@@ -67,8 +67,6 @@ class Account < ApplicationRecord
|
||||
include AccountCounters
|
||||
include DomainNormalizable
|
||||
|
||||
enum protocol: [:ostatus, :activitypub]
|
||||
|
||||
validates :username, presence: true
|
||||
|
||||
# Remote user validations
|
||||
@@ -85,7 +83,6 @@ class Account < ApplicationRecord
|
||||
|
||||
scope :remote, -> { where.not(domain: nil) }
|
||||
scope :local, -> { where(domain: nil) }
|
||||
scope :expiring, ->(time) { remote.where.not(subscription_expires_at: nil).where('subscription_expires_at < ?', time) }
|
||||
scope :partitioned, -> { order(Arel.sql('row_number() over (partition by domain)')) }
|
||||
scope :silenced, -> { where.not(silenced_at: nil) }
|
||||
scope :suspended, -> { where.not(suspended_at: nil) }
|
||||
@@ -110,13 +107,11 @@ class Account < ApplicationRecord
|
||||
:current_sign_in_at,
|
||||
:confirmed?,
|
||||
:approved?,
|
||||
:pending?,
|
||||
:admin?,
|
||||
:moderator?,
|
||||
:staff?,
|
||||
:locale,
|
||||
:hides_network?,
|
||||
:shows_application?,
|
||||
to: :user,
|
||||
prefix: true,
|
||||
allow_nil: true
|
||||
@@ -153,23 +148,6 @@ class Account < ApplicationRecord
|
||||
Follow.where(target_account_id: id).count
|
||||
end
|
||||
|
||||
def to_webfinger_s
|
||||
"acct:#{local_username_and_domain}"
|
||||
end
|
||||
|
||||
def subscribed?
|
||||
subscription_expires_at.present?
|
||||
end
|
||||
|
||||
def possibly_stale?
|
||||
last_webfingered_at.nil? || last_webfingered_at <= 1.day.ago
|
||||
end
|
||||
|
||||
def refresh!
|
||||
return if local?
|
||||
ResolveAccountService.new.call(acct)
|
||||
end
|
||||
|
||||
def silenced?
|
||||
silenced_at.present?
|
||||
end
|
||||
@@ -247,10 +225,6 @@ class Account < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def also_known_as
|
||||
self[:also_known_as] || []
|
||||
end
|
||||
|
||||
def fields
|
||||
(self[:fields] || []).map { |f| Field.new(self, f) }
|
||||
end
|
||||
@@ -307,17 +281,11 @@ class Account < ApplicationRecord
|
||||
(['RSA'] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
|
||||
end
|
||||
|
||||
def subscription(webhook_url)
|
||||
@subscription ||= OStatus2::Subscription.new(remote_url, secret: secret, webhook: webhook_url, hub: hub_url)
|
||||
end
|
||||
|
||||
def save_with_optional_media!
|
||||
save!
|
||||
rescue ActiveRecord::RecordInvalid
|
||||
self.avatar = nil
|
||||
self.header = nil
|
||||
self[:avatar_remote_url] = ''
|
||||
self[:header_remote_url] = ''
|
||||
save!
|
||||
end
|
||||
|
||||
@@ -400,11 +368,6 @@ class Account < ApplicationRecord
|
||||
reorder(nil).pluck(Arel.sql('distinct accounts.domain'))
|
||||
end
|
||||
|
||||
def inboxes
|
||||
urls = reorder(nil).where(protocol: :activitypub).pluck(Arel.sql("distinct coalesce(nullif(accounts.shared_inbox_url, ''), accounts.inbox_url)"))
|
||||
DeliveryFailureTracker.filter(urls)
|
||||
end
|
||||
|
||||
def search_for(terms, limit = 10, offset = 0, options = {})
|
||||
textsearch, query = generate_query_for_search(terms)
|
||||
@onlyVerified = options[:onlyVerified] || false
|
||||
|
||||
@@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: account_domain_blocks
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# domain :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# account_id :bigint(8)
|
||||
#
|
||||
|
||||
class AccountDomainBlock < ApplicationRecord
|
||||
include Paginable
|
||||
include DomainNormalizable
|
||||
|
||||
belongs_to :account
|
||||
validates :domain, presence: true, uniqueness: { scope: :account_id }
|
||||
|
||||
after_commit :remove_blocking_cache
|
||||
after_commit :remove_relationship_cache
|
||||
|
||||
private
|
||||
|
||||
def remove_blocking_cache
|
||||
Rails.cache.delete("exclude_domains_for:#{account_id}")
|
||||
end
|
||||
|
||||
def remove_relationship_cache
|
||||
Rails.cache.delete_matched("relationship:#{account_id}:*")
|
||||
end
|
||||
end
|
||||
@@ -1,46 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: account_identity_proofs
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8)
|
||||
# provider :string default(""), not null
|
||||
# provider_username :string default(""), not null
|
||||
# token :text default(""), not null
|
||||
# verified :boolean default(FALSE), not null
|
||||
# live :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class AccountIdentityProof < ApplicationRecord
|
||||
belongs_to :account
|
||||
|
||||
validates :provider, inclusion: { in: ProofProvider::SUPPORTED_PROVIDERS }
|
||||
validates :provider_username, format: { with: /\A[a-z0-9_]+\z/i }, length: { minimum: 2, maximum: 30 }
|
||||
validates :provider_username, uniqueness: { scope: [:account_id, :provider] }
|
||||
validates :token, format: { with: /\A[a-f0-9]+\z/ }, length: { maximum: 66 }
|
||||
|
||||
validate :validate_with_provider, if: :token_changed?
|
||||
|
||||
scope :active, -> { where(verified: true, live: true) }
|
||||
|
||||
after_commit :queue_worker, if: :saved_change_to_token?
|
||||
|
||||
delegate :refresh!, :on_success_path, :badge, to: :provider_instance
|
||||
|
||||
def provider_instance
|
||||
@provider_instance ||= ProofProvider.find(provider, self)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def queue_worker
|
||||
provider_instance.worker_class.perform_async(id)
|
||||
end
|
||||
|
||||
def validate_with_provider
|
||||
provider_instance.validate!
|
||||
end
|
||||
end
|
||||
@@ -1,27 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: account_pins
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8)
|
||||
# target_account_id :bigint(8)
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class AccountPin < ApplicationRecord
|
||||
include Paginable
|
||||
include RelationshipCacheable
|
||||
|
||||
belongs_to :account
|
||||
belongs_to :target_account, class_name: 'Account'
|
||||
|
||||
validate :validate_follow_relationship
|
||||
|
||||
private
|
||||
|
||||
def validate_follow_relationship
|
||||
errors.add(:base, I18n.t('accounts.pin_errors.following')) unless account.following?(target_account)
|
||||
end
|
||||
end
|
||||
@@ -25,7 +25,6 @@ class Block < ApplicationRecord
|
||||
end
|
||||
|
||||
after_commit :remove_blocking_cache
|
||||
before_validation :set_uri, only: :create
|
||||
|
||||
private
|
||||
|
||||
@@ -33,8 +32,4 @@ class Block < ApplicationRecord
|
||||
Rails.cache.delete("exclude_account_ids_for:#{account_id}")
|
||||
Rails.cache.delete("exclude_account_ids_for:#{target_account_id}")
|
||||
end
|
||||
|
||||
def set_uri
|
||||
self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -7,16 +7,11 @@ module AccountAssociations
|
||||
# Local users
|
||||
has_one :user, inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Identity proofs
|
||||
has_many :identity_proofs, class_name: 'AccountIdentityProof', dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Timelines
|
||||
has_many :stream_entries, inverse_of: :account, dependent: :destroy
|
||||
has_many :statuses, inverse_of: :account, dependent: :destroy
|
||||
has_many :favourites, inverse_of: :account, dependent: :destroy
|
||||
has_many :mentions, inverse_of: :account, dependent: :destroy
|
||||
has_many :notifications, inverse_of: :account, dependent: :destroy
|
||||
has_many :conversations, class_name: 'AccountConversation', dependent: :destroy, inverse_of: :account
|
||||
has_many :scheduled_statuses, inverse_of: :account, dependent: :destroy
|
||||
|
||||
# Pinned statuses
|
||||
@@ -27,10 +22,6 @@ module AccountAssociations
|
||||
has_many :status_pins, inverse_of: :account, dependent: :destroy
|
||||
has_many :pinned_statuses, -> { reorder('status_pins.created_at DESC') }, through: :status_pins, class_name: 'Status', source: :status
|
||||
|
||||
# Endorsements
|
||||
has_many :account_pins, inverse_of: :account, dependent: :destroy
|
||||
has_many :endorsed_accounts, through: :account_pins, class_name: 'Account', source: :target_account
|
||||
|
||||
# Media
|
||||
has_many :media_attachments, dependent: :destroy
|
||||
has_many :polls, dependent: :destroy
|
||||
@@ -63,7 +54,6 @@ module AccountAssociations
|
||||
|
||||
# Hashtags
|
||||
has_and_belongs_to_many :tags
|
||||
has_many :featured_tags, -> { includes(:tag) }, dependent: :destroy, inverse_of: :account
|
||||
|
||||
# Billing
|
||||
has_many :transactions, class_name: 'Transaction', dependent: :destroy, inverse_of: :account
|
||||
|
||||
@@ -8,10 +8,6 @@ module AccountFinderConcern
|
||||
find_local(username) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def find_remote!(username, domain)
|
||||
find_remote(username, domain) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
|
||||
def find_acct!(acct)
|
||||
find_acct(acct) || raise(ActiveRecord::RecordNotFound)
|
||||
end
|
||||
@@ -21,25 +17,24 @@ module AccountFinderConcern
|
||||
end
|
||||
|
||||
def find_local(username)
|
||||
find_remote(username, nil)
|
||||
end
|
||||
|
||||
def find_remote(username, domain)
|
||||
AccountFinder.new(username, domain).account
|
||||
find_now(username)
|
||||
end
|
||||
|
||||
def find_acct(acct)
|
||||
username, domain = acct.split("@")
|
||||
find_remote(username, domain)
|
||||
find_now(username)
|
||||
end
|
||||
|
||||
def find_now(username)
|
||||
AccountFinder.new(username).account
|
||||
end
|
||||
end
|
||||
|
||||
class AccountFinder
|
||||
attr_reader :username, :domain
|
||||
attr_reader :username
|
||||
|
||||
def initialize(username, domain)
|
||||
def initialize(username)
|
||||
@username = username
|
||||
@domain = domain
|
||||
end
|
||||
|
||||
def account
|
||||
@@ -65,11 +60,7 @@ module AccountFinderConcern
|
||||
end
|
||||
|
||||
def matching_domain
|
||||
if domain.nil?
|
||||
Account.where(domain: nil)
|
||||
else
|
||||
Account.where(Account.arel_table[:domain].lower.eq domain.to_s.downcase)
|
||||
end
|
||||
Account.where(domain: nil)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
@@ -40,20 +40,6 @@ module AccountInteractions
|
||||
end
|
||||
end
|
||||
|
||||
def endorsed_map(target_account_ids, account_id)
|
||||
follow_mapping(AccountPin.where(account_id: account_id, target_account_id: target_account_ids), :target_account_id)
|
||||
end
|
||||
|
||||
def domain_blocking_map(target_account_ids, account_id)
|
||||
accounts_map = Account.where(id: target_account_ids).select('id, domain').each_with_object({}) { |a, h| h[a.id] = a.domain }
|
||||
blocked_domains = domain_blocking_map_by_domain(accounts_map.values.compact, account_id)
|
||||
accounts_map.reduce({}) { |h, (id, domain)| h.merge(id => blocked_domains[domain]) }
|
||||
end
|
||||
|
||||
def domain_blocking_map_by_domain(target_domains, account_id)
|
||||
follow_mapping(AccountDomainBlock.where(account_id: account_id, domain: target_domains), :domain)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def follow_mapping(query, field)
|
||||
@@ -85,8 +71,6 @@ module AccountInteractions
|
||||
has_many :muting, -> { order('mutes.id desc') }, through: :mute_relationships, source: :target_account
|
||||
has_many :muted_by_relationships, class_name: 'Mute', foreign_key: :target_account_id, dependent: :destroy
|
||||
has_many :muted_by, -> { order('mutes.id desc') }, through: :muted_by_relationships, source: :account
|
||||
has_many :conversation_mutes, dependent: :destroy
|
||||
has_many :domain_blocks, class_name: 'AccountDomainBlock', dependent: :destroy
|
||||
end
|
||||
|
||||
def follow!(other_account, reblogs: nil, uri: nil)
|
||||
@@ -120,14 +104,6 @@ module AccountInteractions
|
||||
mute
|
||||
end
|
||||
|
||||
def mute_conversation!(conversation)
|
||||
conversation_mutes.find_or_create_by!(conversation: conversation)
|
||||
end
|
||||
|
||||
def block_domain!(other_domain)
|
||||
domain_blocks.find_or_create_by!(domain: other_domain)
|
||||
end
|
||||
|
||||
def unfollow!(other_account)
|
||||
follow = active_relationships.find_by(target_account: other_account)
|
||||
follow&.destroy
|
||||
@@ -143,16 +119,6 @@ module AccountInteractions
|
||||
mute&.destroy
|
||||
end
|
||||
|
||||
def unmute_conversation!(conversation)
|
||||
mute = conversation_mutes.find_by(conversation: conversation)
|
||||
mute&.destroy!
|
||||
end
|
||||
|
||||
def unblock_domain!(other_domain)
|
||||
block = domain_blocks.find_by(domain: other_domain)
|
||||
block&.destroy
|
||||
end
|
||||
|
||||
def following?(other_account)
|
||||
active_relationships.where(target_account: other_account).exists?
|
||||
end
|
||||
@@ -161,18 +127,10 @@ module AccountInteractions
|
||||
block_relationships.where(target_account: other_account).exists?
|
||||
end
|
||||
|
||||
def domain_blocking?(other_domain)
|
||||
domain_blocks.where(domain: other_domain).exists?
|
||||
end
|
||||
|
||||
def muting?(other_account)
|
||||
mute_relationships.where(target_account: other_account).exists?
|
||||
end
|
||||
|
||||
def muting_conversation?(conversation)
|
||||
conversation_mutes.where(conversation: conversation).exists?
|
||||
end
|
||||
|
||||
def muting_notifications?(other_account)
|
||||
mute_relationships.where(target_account: other_account, hide_notifications: true).exists?
|
||||
end
|
||||
@@ -201,10 +159,6 @@ module AccountInteractions
|
||||
status_pins.where(status: status).exists?
|
||||
end
|
||||
|
||||
def endorsed?(account)
|
||||
account_pins.where(target_account: account).exists?
|
||||
end
|
||||
|
||||
def followers_for_local_distribution
|
||||
followers.local
|
||||
.joins(:user)
|
||||
|
||||
@@ -44,9 +44,9 @@ module Omniauthable
|
||||
# verified email. If no verified email was provided or the user already
|
||||
# exists, we assign a temporary email and ask the user to verify it on
|
||||
# the next step via Auth::ConfirmationsController.finish_signup
|
||||
|
||||
|
||||
# : TODO : remove, no sign up from app. only website
|
||||
user = User.new(user_params_from_auth(auth))
|
||||
user.account.avatar_remote_url = auth.info.image if auth.info.image =~ /\A#{URI.regexp(%w(http https))}\z/
|
||||
user.skip_confirmation!
|
||||
user.save!
|
||||
user
|
||||
|
||||
@@ -122,7 +122,6 @@ module StatusThreadingConcern
|
||||
blocked_by: Account.blocked_by_map(account_ids, account.id),
|
||||
muting: Account.muting_map(account_ids, account.id),
|
||||
following: Account.following_map(account_ids, account.id),
|
||||
domain_blocking_by_domain: Account.domain_blocking_map_by_domain(domains, account.id),
|
||||
}
|
||||
end
|
||||
|
||||
|
||||
@@ -1,43 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
module Streamable
|
||||
extend ActiveSupport::Concern
|
||||
|
||||
included do
|
||||
has_one :stream_entry, as: :activity
|
||||
|
||||
after_create do
|
||||
account.stream_entries.create!(activity: self, hidden: hidden?) if needs_stream_entry?
|
||||
end
|
||||
end
|
||||
|
||||
def title
|
||||
super
|
||||
end
|
||||
|
||||
def content
|
||||
title
|
||||
end
|
||||
|
||||
def target
|
||||
super
|
||||
end
|
||||
|
||||
def object_type
|
||||
:activity
|
||||
end
|
||||
|
||||
def thread
|
||||
super
|
||||
end
|
||||
|
||||
def hidden?
|
||||
false
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def needs_stream_entry?
|
||||
account.local?
|
||||
end
|
||||
end
|
||||
@@ -1,14 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: conversation_mutes
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# conversation_id :bigint(8) not null
|
||||
# account_id :bigint(8) not null
|
||||
#
|
||||
|
||||
class ConversationMute < ApplicationRecord
|
||||
belongs_to :account
|
||||
belongs_to :conversation
|
||||
end
|
||||
@@ -12,7 +12,6 @@
|
||||
# image_updated_at :datetime
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# disabled :boolean default(FALSE), not null
|
||||
# uri :string
|
||||
# image_remote_url :string
|
||||
# visible_in_picker :boolean default(TRUE), not null
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# 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
|
||||
# reject_reports :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class DomainBlock < ApplicationRecord
|
||||
include DomainNormalizable
|
||||
|
||||
enum severity: [:silence, :suspend, :noop]
|
||||
|
||||
validates :domain, presence: true, uniqueness: true
|
||||
|
||||
has_many :accounts, foreign_key: :domain, primary_key: :domain
|
||||
delegate :count, to: :accounts, prefix: true
|
||||
|
||||
scope :matches_domain, ->(value) { where(arel_table[:domain].matches("%#{value}%")) }
|
||||
|
||||
def self.blocked?(domain)
|
||||
where(domain: domain, severity: :suspend).exists?
|
||||
end
|
||||
|
||||
def stricter_than?(other_block)
|
||||
return true if suspend?
|
||||
return false if other_block.suspend? && (silence? || noop?)
|
||||
return false if other_block.silence? && noop?
|
||||
(reject_media || !other_block.reject_media) && (reject_reports || !other_block.reject_reports)
|
||||
end
|
||||
|
||||
def affected_accounts_count
|
||||
scope = suspend? ? accounts.where(suspended_at: created_at) : accounts.where(silenced_at: created_at)
|
||||
scope.count
|
||||
end
|
||||
end
|
||||
@@ -39,14 +39,6 @@ class Export
|
||||
end
|
||||
end
|
||||
|
||||
def to_blocked_domains_csv
|
||||
CSV.generate do |csv|
|
||||
account.domain_blocks.pluck(:domain).each do |domain|
|
||||
csv << [domain]
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
def total_storage
|
||||
account.media_attachments.sum(:file_file_size)
|
||||
end
|
||||
@@ -75,10 +67,6 @@ class Export
|
||||
account.muting.count
|
||||
end
|
||||
|
||||
def total_domain_blocks
|
||||
account.domain_blocks.count
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def to_csv(accounts)
|
||||
|
||||
@@ -1,47 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: featured_tags
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# account_id :bigint(8)
|
||||
# tag_id :bigint(8)
|
||||
# statuses_count :bigint(8) default(0), not null
|
||||
# last_status_at :datetime
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class FeaturedTag < ApplicationRecord
|
||||
belongs_to :account, inverse_of: :featured_tags, required: true
|
||||
belongs_to :tag, inverse_of: :featured_tags, required: true
|
||||
|
||||
delegate :name, to: :tag, allow_nil: true
|
||||
|
||||
validates_associated :tag, on: :create
|
||||
validates :name, presence: true, on: :create
|
||||
validate :validate_featured_tags_limit, on: :create
|
||||
|
||||
def name=(str)
|
||||
self.tag = Tag.find_or_initialize_by(name: str.strip.delete('#').mb_chars.downcase.to_s)
|
||||
end
|
||||
|
||||
def increment(timestamp)
|
||||
update(statuses_count: statuses_count + 1, last_status_at: timestamp)
|
||||
end
|
||||
|
||||
def decrement(deleted_status_id)
|
||||
update(statuses_count: [0, statuses_count - 1].max, last_status_at: account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).where.not(id: deleted_status_id).select(:created_at).first&.created_at)
|
||||
end
|
||||
|
||||
def reset_data
|
||||
self.statuses_count = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).count
|
||||
self.last_status_at = account.statuses.where(visibility: %i(public unlisted)).tagged_with(tag).select(:created_at).first&.created_at
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def validate_featured_tags_limit
|
||||
errors.add(:base, I18n.t('featured_tags.errors.limit')) if account.featured_tags.count >= 10
|
||||
end
|
||||
end
|
||||
@@ -31,25 +31,15 @@ class Follow < ApplicationRecord
|
||||
end
|
||||
|
||||
def revoke_request!
|
||||
FollowRequest.create!(account: account, target_account: target_account, show_reblogs: show_reblogs, uri: uri)
|
||||
FollowRequest.create!(account: account, target_account: target_account)
|
||||
destroy!
|
||||
end
|
||||
|
||||
before_validation :set_uri, only: :create
|
||||
after_create :increment_cache_counters
|
||||
after_destroy :remove_endorsements
|
||||
after_destroy :decrement_cache_counters
|
||||
|
||||
private
|
||||
|
||||
def set_uri
|
||||
self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil?
|
||||
end
|
||||
|
||||
def remove_endorsements
|
||||
AccountPin.where(target_account_id: target_account_id, account_id: account_id).delete_all
|
||||
end
|
||||
|
||||
def increment_cache_counters
|
||||
account&.increment_count!(:following_count)
|
||||
target_account&.increment_count!(:followers_count)
|
||||
|
||||
@@ -25,7 +25,7 @@ class FollowRequest < ApplicationRecord
|
||||
validates_with FollowLimitValidator, on: :create
|
||||
|
||||
def authorize!
|
||||
account.follow!(target_account, reblogs: show_reblogs, uri: uri)
|
||||
account.follow!(target_account)
|
||||
MergeWorker.perform_async(target_account.id, account.id) if account.local?
|
||||
destroy!
|
||||
end
|
||||
@@ -33,14 +33,6 @@ class FollowRequest < ApplicationRecord
|
||||
alias reject! destroy!
|
||||
|
||||
def local?
|
||||
false # Force uri_for to use uri attribute
|
||||
end
|
||||
|
||||
before_validation :set_uri, only: :create
|
||||
|
||||
private
|
||||
|
||||
def set_uri
|
||||
self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil?
|
||||
false
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,8 +12,6 @@ class Form::AccountBatch
|
||||
unfollow!
|
||||
when 'remove_from_followers'
|
||||
remove_from_followers!
|
||||
when 'block_domains'
|
||||
block_domains!
|
||||
when 'approve'
|
||||
approve!
|
||||
when 'reject'
|
||||
@@ -35,12 +33,6 @@ class Form::AccountBatch
|
||||
end
|
||||
end
|
||||
|
||||
def block_domains!
|
||||
AfterAccountDomainBlockWorker.push_bulk(account_domains) do |domain|
|
||||
[current_account.id, domain]
|
||||
end
|
||||
end
|
||||
|
||||
def account_domains
|
||||
accounts.pluck(Arel.sql('distinct domain')).compact
|
||||
end
|
||||
|
||||
@@ -9,47 +9,25 @@ class Form::AdminSettings
|
||||
site_title
|
||||
site_short_description
|
||||
site_description
|
||||
site_extended_description
|
||||
site_terms
|
||||
registrations_mode
|
||||
closed_registrations_message
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
bootstrap_timeline_accounts
|
||||
theme
|
||||
min_invite_role
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
preview_sensitive_media
|
||||
profile_directory
|
||||
thumbnail
|
||||
hero
|
||||
mascot
|
||||
).freeze
|
||||
|
||||
BOOLEAN_KEYS = %i(
|
||||
open_deletion
|
||||
timeline_preview
|
||||
show_staff_badge
|
||||
activity_api_enabled
|
||||
peers_api_enabled
|
||||
show_known_fediverse_at_about_page
|
||||
preview_sensitive_media
|
||||
profile_directory
|
||||
).freeze
|
||||
|
||||
UPLOAD_KEYS = %i(
|
||||
thumbnail
|
||||
hero
|
||||
mascot
|
||||
).freeze
|
||||
|
||||
attr_accessor(*KEYS)
|
||||
|
||||
validates :site_short_description, :site_description, html: { wrap_with: :p }
|
||||
validates :site_extended_description, :site_terms, :closed_registrations_message, html: true
|
||||
validates :registrations_mode, inclusion: { in: %w(open approved none) }
|
||||
validates :min_invite_role, inclusion: { in: %w(disabled user moderator admin) }
|
||||
validates :site_contact_email, :site_contact_username, presence: true
|
||||
|
||||
@@ -20,6 +20,6 @@ class Form::Migration
|
||||
private
|
||||
|
||||
def set_account
|
||||
self.account = (ResolveAccountService.new.call(acct) if account.nil? && acct.present?)
|
||||
self.account = nil
|
||||
end
|
||||
end
|
||||
|
||||
@@ -56,6 +56,7 @@ class Group < ApplicationRecord
|
||||
record.errors.add(:base, I18n.t('groups.errors.limit')) if Group.where(account_id: value).count >= PER_ACCOUNT_LIMIT
|
||||
end
|
||||
|
||||
before_save :set_slug
|
||||
before_save :set_password
|
||||
before_destroy :clean_feed_manager
|
||||
after_create :add_owner_to_accounts
|
||||
@@ -80,6 +81,20 @@ class Group < ApplicationRecord
|
||||
password
|
||||
end
|
||||
end
|
||||
|
||||
def set_slug
|
||||
puts "tilly-5: " + slug.to_s
|
||||
puts "tilly-6: " + self.member_count.to_s
|
||||
|
||||
if !slug.nil? && self.member_count > 50 && self.slug.nil?
|
||||
self.slug = slug.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '')
|
||||
elsif !slug.nil? && self.member_count < 50 && self.slug.nil?
|
||||
self.slug = nil
|
||||
else
|
||||
self.slug = self.slug
|
||||
end
|
||||
end
|
||||
|
||||
def add_owner_to_accounts
|
||||
group_accounts << GroupAccount.new(account: account, role: :admin, write_permissions: true)
|
||||
end
|
||||
|
||||
@@ -13,6 +13,7 @@
|
||||
|
||||
class GroupAccount < ApplicationRecord
|
||||
self.ignored_columns = ["unread_count"]
|
||||
# : todo : enum: 1,2,3,4,5...
|
||||
enum role: {
|
||||
admin: "admin",
|
||||
moderator: "moderator"
|
||||
|
||||
@@ -1,42 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: imports
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# type :integer not null
|
||||
# approved :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# data_file_name :string
|
||||
# data_content_type :string
|
||||
# data_file_size :integer
|
||||
# data_updated_at :datetime
|
||||
# account_id :bigint(8) not null
|
||||
# overwrite :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class Import < ApplicationRecord
|
||||
FILE_TYPES = %w(text/plain text/csv).freeze
|
||||
MODES = %i(merge overwrite).freeze
|
||||
|
||||
self.inheritance_column = false
|
||||
|
||||
belongs_to :account
|
||||
|
||||
enum type: [:following, :blocking, :muting, :domain_blocking]
|
||||
|
||||
validates :type, presence: true
|
||||
|
||||
has_attached_file :data
|
||||
validates_attachment_content_type :data, content_type: FILE_TYPES
|
||||
validates_attachment_presence :data
|
||||
|
||||
def mode
|
||||
overwrite? ? :overwrite : :merge
|
||||
end
|
||||
|
||||
def mode=(str)
|
||||
self.overwrite = str.to_sym == :overwrite
|
||||
end
|
||||
end
|
||||
@@ -1,39 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: invites
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# user_id :bigint(8) not null
|
||||
# code :string default(""), not null
|
||||
# expires_at :datetime
|
||||
# max_uses :integer
|
||||
# uses :integer default(0), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# autofollow :boolean default(FALSE), not null
|
||||
#
|
||||
|
||||
class Invite < ApplicationRecord
|
||||
include Expireable
|
||||
|
||||
belongs_to :user
|
||||
has_many :users, inverse_of: :invite
|
||||
|
||||
scope :available, -> { where(expires_at: nil).or(where('expires_at >= ?', Time.now.utc)) }
|
||||
|
||||
before_validation :set_code
|
||||
|
||||
def valid_for_use?
|
||||
(max_uses.nil? || uses < max_uses) && !expired?
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_code
|
||||
loop do
|
||||
self.code = ([*('a'..'z'), *('A'..'Z'), *('0'..'9')] - %w(0 1 I l O)).sample(8).join
|
||||
break if Invite.find_by(code: code).nil?
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -1,32 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class InviteFilter
|
||||
attr_reader :params
|
||||
|
||||
def initialize(params)
|
||||
@params = params
|
||||
end
|
||||
|
||||
def results
|
||||
scope = Invite.order(created_at: :desc)
|
||||
|
||||
params.each do |key, value|
|
||||
scope.merge!(scope_for(key, value)) if value.present?
|
||||
end
|
||||
|
||||
scope
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def scope_for(key, _value)
|
||||
case key.to_s
|
||||
when 'available'
|
||||
Invite.available
|
||||
when 'expired'
|
||||
Invite.expired
|
||||
else
|
||||
raise "Unknown filter: #{key}"
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -16,6 +16,7 @@ class Notification < ApplicationRecord
|
||||
include Paginable
|
||||
include Cacheable
|
||||
|
||||
# : todo : enums, 1,2,3,4,5,...
|
||||
TYPE_CLASS_MAP = {
|
||||
mention: 'Mention',
|
||||
reblog: 'Status',
|
||||
|
||||
@@ -46,10 +46,6 @@ class Poll < ApplicationRecord
|
||||
options.map.with_index { |title, key| Option.new(self, key.to_s, title, show_totals_now? ? cached_tallies[key] : nil) }
|
||||
end
|
||||
|
||||
def possibly_stale?
|
||||
remote? && last_fetched_before_expiration? && time_passed_since_last_fetch?
|
||||
end
|
||||
|
||||
def voted?(account)
|
||||
account.id == account_id || votes.where(account: account).exists?
|
||||
end
|
||||
@@ -94,15 +90,7 @@ class Poll < ApplicationRecord
|
||||
Rails.cache.delete("statuses/#{status_id}")
|
||||
end
|
||||
|
||||
def last_fetched_before_expiration?
|
||||
last_fetched_at.nil? || expires_at.nil? || last_fetched_at < expires_at
|
||||
end
|
||||
|
||||
def time_passed_since_last_fetch?
|
||||
last_fetched_at.nil? || last_fetched_at < 1.minute.ago
|
||||
end
|
||||
|
||||
def show_totals_now?
|
||||
expired? || !hide_totals?
|
||||
expired?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,80 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: relays
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# inbox_url :string default(""), not null
|
||||
# follow_activity_id :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# state :integer default("idle"), not null
|
||||
#
|
||||
|
||||
class Relay < ApplicationRecord
|
||||
PRESET_RELAY = 'https://relay.gab.com/inbox'
|
||||
|
||||
validates :inbox_url, presence: true, uniqueness: true, url: true, if: :will_save_change_to_inbox_url?
|
||||
|
||||
enum state: [:idle, :pending, :accepted, :rejected]
|
||||
|
||||
scope :enabled, -> { accepted }
|
||||
|
||||
before_destroy :ensure_disabled
|
||||
|
||||
alias enabled? accepted?
|
||||
|
||||
def enable!
|
||||
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
|
||||
payload = Oj.dump(follow_activity(activity_id))
|
||||
|
||||
update!(state: :pending, follow_activity_id: activity_id)
|
||||
DeliveryFailureTracker.new(inbox_url).track_success!
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
def disable!
|
||||
activity_id = ActivityPub::TagManager.instance.generate_uri_for(nil)
|
||||
payload = Oj.dump(unfollow_activity(activity_id))
|
||||
|
||||
update!(state: :idle, follow_activity_id: nil)
|
||||
DeliveryFailureTracker.new(inbox_url).track_success!
|
||||
ActivityPub::DeliveryWorker.perform_async(payload, some_local_account.id, inbox_url)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def follow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: activity_id,
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||
}
|
||||
end
|
||||
|
||||
def unfollow_activity(activity_id)
|
||||
{
|
||||
'@context': ActivityPub::TagManager::CONTEXT,
|
||||
id: activity_id,
|
||||
type: 'Undo',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: {
|
||||
id: follow_activity_id,
|
||||
type: 'Follow',
|
||||
actor: ActivityPub::TagManager.instance.uri_for(some_local_account),
|
||||
object: ActivityPub::TagManager::COLLECTIONS[:public],
|
||||
},
|
||||
}
|
||||
end
|
||||
|
||||
def some_local_account
|
||||
@some_local_account ||= Account.representative
|
||||
end
|
||||
|
||||
def ensure_disabled
|
||||
return unless enabled?
|
||||
disable!
|
||||
end
|
||||
end
|
||||
@@ -1,56 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoteFollow
|
||||
include ActiveModel::Validations
|
||||
|
||||
attr_accessor :acct, :addressable_template
|
||||
|
||||
validates :acct, presence: true
|
||||
|
||||
def initialize(attrs = nil)
|
||||
@acct = attrs[:acct].gsub(/\A@/, '').strip if !attrs.nil? && !attrs[:acct].nil?
|
||||
end
|
||||
|
||||
def valid?
|
||||
return false unless super
|
||||
|
||||
populate_template
|
||||
errors.empty?
|
||||
end
|
||||
|
||||
def subscribe_address_for(account)
|
||||
addressable_template.expand(uri: account.local_username_and_domain).to_s
|
||||
end
|
||||
|
||||
def interact_address_for(status)
|
||||
addressable_template.expand(uri: ActivityPub::TagManager.instance.uri_for(status)).to_s
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def populate_template
|
||||
if acct.blank? || redirect_url_link.nil? || redirect_url_link.template.nil?
|
||||
missing_resource_error
|
||||
else
|
||||
@addressable_template = Addressable::Template.new(redirect_uri_template)
|
||||
end
|
||||
end
|
||||
|
||||
def redirect_uri_template
|
||||
redirect_url_link.template
|
||||
end
|
||||
|
||||
def redirect_url_link
|
||||
acct_resource&.link('http://ostatus.org/schema/1.0/subscribe')
|
||||
end
|
||||
|
||||
def acct_resource
|
||||
@_acct_resource ||= Goldfinger.finger("acct:#{acct}")
|
||||
rescue Goldfinger::Error, HTTP::ConnectionError
|
||||
nil
|
||||
end
|
||||
|
||||
def missing_resource_error
|
||||
errors.add(:acct, I18n.t('remote_follow.missing_resource'))
|
||||
end
|
||||
end
|
||||
@@ -1,57 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class RemoteProfile
|
||||
include ActiveModel::Model
|
||||
|
||||
attr_reader :document
|
||||
|
||||
def initialize(body)
|
||||
@document = Nokogiri::XML.parse(body, nil, 'utf-8')
|
||||
end
|
||||
|
||||
def root
|
||||
@root ||= document.at_xpath('/atom:feed|/atom:entry', atom: OStatus::TagManager::XMLNS)
|
||||
end
|
||||
|
||||
def author
|
||||
@author ||= root.at_xpath('./atom:author|./dfrn:owner', atom: OStatus::TagManager::XMLNS, dfrn: OStatus::TagManager::DFRN_XMLNS)
|
||||
end
|
||||
|
||||
def hub_link
|
||||
@hub_link ||= link_href_from_xml(root, 'hub')
|
||||
end
|
||||
|
||||
def display_name
|
||||
@display_name ||= author.at_xpath('./poco:displayName', poco: OStatus::TagManager::POCO_XMLNS)&.content
|
||||
end
|
||||
|
||||
def note
|
||||
@note ||= author.at_xpath('./atom:summary|./poco:note', atom: OStatus::TagManager::XMLNS, poco: OStatus::TagManager::POCO_XMLNS)&.content
|
||||
end
|
||||
|
||||
def scope
|
||||
@scope ||= author.at_xpath('./mastodon:scope', mastodon: OStatus::TagManager::MTDN_XMLNS)&.content
|
||||
end
|
||||
|
||||
def avatar
|
||||
@avatar ||= link_href_from_xml(author, 'avatar')
|
||||
end
|
||||
|
||||
def header
|
||||
@header ||= link_href_from_xml(author, 'header')
|
||||
end
|
||||
|
||||
def emojis
|
||||
@emojis ||= author.xpath('./xmlns:link[@rel="emoji"]', xmlns: OStatus::TagManager::XMLNS)
|
||||
end
|
||||
|
||||
def locked?
|
||||
scope == 'private'
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def link_href_from_xml(xml, type)
|
||||
xml.at_xpath(%(./atom:link[@rel="#{type}"]/@href), atom: OStatus::TagManager::XMLNS)&.content
|
||||
end
|
||||
end
|
||||
@@ -33,8 +33,6 @@ class Report < ApplicationRecord
|
||||
false # Force uri_for to use uri attribute
|
||||
end
|
||||
|
||||
before_validation :set_uri, only: :create
|
||||
|
||||
def object_type
|
||||
:flag
|
||||
end
|
||||
@@ -97,7 +95,4 @@ class Report < ApplicationRecord
|
||||
Admin::ActionLog.from("(#{sql}) AS admin_action_logs")
|
||||
end
|
||||
|
||||
def set_uri
|
||||
self.uri = ActivityPub::TagManager.instance.generate_uri_for(self) if uri.nil? && account.local?
|
||||
end
|
||||
end
|
||||
|
||||
@@ -12,6 +12,7 @@
|
||||
#
|
||||
|
||||
class Shortcut < ApplicationRecord
|
||||
# : todo : enum 1,2, etc.
|
||||
# enum shortcut_type: {
|
||||
# account: 'account',
|
||||
# group: 'group'
|
||||
|
||||
@@ -34,7 +34,6 @@ class Status < ApplicationRecord
|
||||
before_destroy :unlink_from_conversations
|
||||
|
||||
include Paginable
|
||||
include Streamable
|
||||
include Cacheable
|
||||
include StatusThreadingConcern
|
||||
|
||||
@@ -48,7 +47,6 @@ class Status < ApplicationRecord
|
||||
:public,
|
||||
:unlisted,
|
||||
:private,
|
||||
:direct,
|
||||
:limited,
|
||||
:private_group,
|
||||
], _suffix: :visibility
|
||||
@@ -79,16 +77,14 @@ class Status < ApplicationRecord
|
||||
has_and_belongs_to_many :preview_cards
|
||||
|
||||
has_one :notification, as: :activity, dependent: :destroy
|
||||
has_one :stream_entry, as: :activity, inverse_of: :status
|
||||
has_one :status_stat, inverse_of: :status
|
||||
has_one :poll, inverse_of: :status, dependent: :destroy
|
||||
|
||||
validates :uri, uniqueness: true, presence: true, unless: :local?
|
||||
validates :text, presence: true, unless: -> { with_media? || reblog? }
|
||||
validates_with StatusLengthValidator
|
||||
validates_with DisallowedHashtagsValidator
|
||||
validates :reblog, uniqueness: { scope: :account }, if: :reblog?
|
||||
validates :visibility, exclusion: { in: %w(direct limited) }, if: :reblog?
|
||||
validates :visibility, exclusion: { in: %w(limited) }, if: :reblog?
|
||||
|
||||
accepts_nested_attributes_for :poll
|
||||
|
||||
@@ -127,13 +123,11 @@ class Status < ApplicationRecord
|
||||
:status_stat,
|
||||
:tags,
|
||||
:preview_cards,
|
||||
:stream_entry,
|
||||
:preloadable_poll,
|
||||
account: :account_stat,
|
||||
active_mentions: { account: :account_stat },
|
||||
reblog: [
|
||||
:application,
|
||||
:stream_entry,
|
||||
:tags,
|
||||
:preview_cards,
|
||||
:media_attachments,
|
||||
@@ -147,8 +141,6 @@ class Status < ApplicationRecord
|
||||
|
||||
delegate :domain, to: :account, prefix: true
|
||||
|
||||
REAL_TIME_WINDOW = 6.hours
|
||||
|
||||
def searchable_by(preloaded = nil)
|
||||
ids = [account_id]
|
||||
|
||||
@@ -181,10 +173,6 @@ class Status < ApplicationRecord
|
||||
!quote_of_id.nil?
|
||||
end
|
||||
|
||||
def within_realtime_window?
|
||||
created_at >= REAL_TIME_WINDOW.ago
|
||||
end
|
||||
|
||||
def verb
|
||||
if destroyed?
|
||||
:delete
|
||||
@@ -222,7 +210,7 @@ class Status < ApplicationRecord
|
||||
end
|
||||
|
||||
def hidden?
|
||||
private_visibility? || private_group_visibility? || direct_visibility? || limited_visibility?
|
||||
private_visibility? || private_group_visibility? || limited_visibility?
|
||||
end
|
||||
|
||||
def distributable?
|
||||
@@ -294,7 +282,7 @@ class Status < ApplicationRecord
|
||||
|
||||
class << self
|
||||
def selectable_visibilities
|
||||
visibilities.keys - %w(direct limited private_group)
|
||||
visibilities.keys - %w(limited private_group)
|
||||
end
|
||||
|
||||
def in_chosen_languages(account)
|
||||
@@ -315,57 +303,11 @@ class Status < ApplicationRecord
|
||||
where(group: groupIds, reply: false)
|
||||
end
|
||||
|
||||
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 queries are executed with pagination.
|
||||
# constant expression using arel_table is required for partial index
|
||||
|
||||
# _from_me part does not require any timeline filters
|
||||
query_from_me = where(account_id: account.id)
|
||||
.where(Status.arel_table[:visibility].eq(3))
|
||||
.limit(limit)
|
||||
.order('statuses.id DESC')
|
||||
|
||||
# _to_me part requires mute and block filter.
|
||||
# FIXME: may we check mutes.hide_notifications?
|
||||
query_to_me = Status
|
||||
.joins(:mentions)
|
||||
.merge(Mention.where(account_id: account.id))
|
||||
.where(Status.arel_table[:visibility].eq(3))
|
||||
.limit(limit)
|
||||
.order('mentions.status_id DESC')
|
||||
.not_excluded_by_account(account)
|
||||
|
||||
if max_id.present?
|
||||
query_from_me = query_from_me.where('statuses.id < ?', max_id)
|
||||
query_to_me = query_to_me.where('mentions.status_id < ?', max_id)
|
||||
end
|
||||
|
||||
if since_id.present?
|
||||
query_from_me = query_from_me.where('statuses.id > ?', since_id)
|
||||
query_to_me = query_to_me.where('mentions.status_id > ?', since_id)
|
||||
end
|
||||
|
||||
if cache_ids
|
||||
# returns array of cache_ids object that have id and updated_at
|
||||
(query_from_me.cache_ids.to_a + query_to_me.cache_ids.to_a).uniq(&:id).sort_by(&:id).reverse.take(limit)
|
||||
else
|
||||
# returns ActiveRecord.Relation
|
||||
items = (query_from_me.select(:id).to_a + query_to_me.select(:id).to_a).uniq(&:id).sort_by(&:id).reverse.take(limit)
|
||||
Status.where(id: items.map(&:id))
|
||||
end
|
||||
end
|
||||
|
||||
def as_pro_timeline(account = nil)
|
||||
query = timeline_scope.without_replies.popular_accounts.where('statuses.updated_at > ?', 2.hours.ago)
|
||||
apply_timeline_filters(query, account)
|
||||
end
|
||||
|
||||
def as_public_timeline(account = nil)
|
||||
query = timeline_scope.without_replies.where('statuses.updated_at > ?', 15.minutes.ago)
|
||||
apply_timeline_filters(query, account)
|
||||
end
|
||||
|
||||
def as_tag_timeline(tag, account = nil)
|
||||
query = timeline_scope.tagged_with(tag).without_replies
|
||||
|
||||
@@ -388,10 +330,6 @@ class Status < ApplicationRecord
|
||||
select('reblog_of_id').where(reblog_of_id: status_ids).where(account_id: account_id).reorder(nil).each_with_object({}) { |s, h| h[s.reblog_of_id] = true }
|
||||
end
|
||||
|
||||
def mutes_map(conversation_ids, account_id)
|
||||
ConversationMute.select('conversation_id').where(conversation_id: conversation_ids).where(account_id: account_id).each_with_object({}) { |m, h| h[m.conversation_id] = true }
|
||||
end
|
||||
|
||||
def pins_map(status_ids, account_id)
|
||||
StatusPin.select('status_id').where(status_id: status_ids).where(account_id: account_id).each_with_object({}) { |p, h| h[p.status_id] = true }
|
||||
end
|
||||
@@ -490,7 +428,7 @@ class Status < ApplicationRecord
|
||||
end
|
||||
|
||||
def store_uri
|
||||
update_column(:uri, ActivityPub::TagManager.instance.uri_for(self)) if uri.nil?
|
||||
update_column(:uri, "/#{self.account.username}/posts/#{self.id}") if uri.nil?
|
||||
end
|
||||
|
||||
def prepare_contents
|
||||
@@ -556,15 +494,13 @@ class Status < ApplicationRecord
|
||||
end
|
||||
|
||||
def increment_counter_caches
|
||||
return if direct_visibility?
|
||||
|
||||
account&.increment_count!(:statuses_count)
|
||||
reblog&.increment_count!(:reblogs_count) if reblog? && (public_visibility? || unlisted_visibility?)
|
||||
thread&.increment_count!(:replies_count) if in_reply_to_id.present? && (public_visibility? || unlisted_visibility?)
|
||||
end
|
||||
|
||||
def decrement_counter_caches
|
||||
return if direct_visibility? || marked_for_mass_destruction?
|
||||
return if marked_for_mass_destruction?
|
||||
|
||||
account&.decrement_count!(:statuses_count)
|
||||
reblog&.decrement_count!(:reblogs_count) if reblog? && (public_visibility? || unlisted_visibility?)
|
||||
@@ -572,14 +508,14 @@ class Status < ApplicationRecord
|
||||
end
|
||||
|
||||
def unlink_from_conversations
|
||||
return unless direct_visibility?
|
||||
# return unless direct_visibility?
|
||||
|
||||
mentioned_accounts = mentions.includes(:account).map(&:account)
|
||||
inbox_owners = mentioned_accounts.select(&:local?) + (account.local? ? [account] : [])
|
||||
# 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
|
||||
# inbox_owners.each do |inbox_owner|
|
||||
# AccountConversation.remove_status(inbox_owner, self)
|
||||
# end
|
||||
end
|
||||
|
||||
end
|
||||
|
||||
@@ -1,59 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: stream_entries
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# activity_id :bigint(8)
|
||||
# activity_type :string
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# hidden :boolean default(FALSE), not null
|
||||
# account_id :bigint(8)
|
||||
#
|
||||
|
||||
class StreamEntry < ApplicationRecord
|
||||
include Paginable
|
||||
|
||||
belongs_to :account, inverse_of: :stream_entries
|
||||
belongs_to :activity, polymorphic: true
|
||||
belongs_to :status, foreign_type: 'Status', foreign_key: 'activity_id', inverse_of: :stream_entry
|
||||
|
||||
validates :account, :activity, presence: true
|
||||
|
||||
STATUS_INCLUDES = [:account, :stream_entry, :conversation, :media_attachments, :tags, mentions: :account, reblog: [:stream_entry, :account, :conversation, :media_attachments, :tags, mentions: :account], thread: [:stream_entry, :account]].freeze
|
||||
|
||||
default_scope { where(activity_type: 'Status') }
|
||||
scope :recent, -> { reorder(id: :desc) }
|
||||
scope :with_includes, -> { includes(:account, status: STATUS_INCLUDES) }
|
||||
|
||||
delegate :target, :title, :content, :thread,
|
||||
to: :status,
|
||||
allow_nil: true
|
||||
|
||||
def object_type
|
||||
orphaned? || targeted? ? :activity : status.object_type
|
||||
end
|
||||
|
||||
def verb
|
||||
orphaned? ? :delete : status.verb
|
||||
end
|
||||
|
||||
def targeted?
|
||||
[:follow, :request_friend, :authorize, :reject, :unfollow, :block, :unblock, :share, :favorite].include? verb
|
||||
end
|
||||
|
||||
def threaded?
|
||||
(verb == :favorite || object_type == :comment) && !thread.nil?
|
||||
end
|
||||
|
||||
def mentions
|
||||
orphaned? ? [] : status.active_mentions.map(&:account)
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def orphaned?
|
||||
status.nil?
|
||||
end
|
||||
end
|
||||
@@ -1,62 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: subscriptions
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# callback_url :string default(""), not null
|
||||
# secret :string
|
||||
# expires_at :datetime
|
||||
# confirmed :boolean default(FALSE), not null
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
# last_successful_delivery_at :datetime
|
||||
# domain :string
|
||||
# account_id :bigint(8) not null
|
||||
#
|
||||
|
||||
class Subscription < ApplicationRecord
|
||||
MIN_EXPIRATION = 1.day.to_i
|
||||
MAX_EXPIRATION = 30.days.to_i
|
||||
|
||||
belongs_to :account
|
||||
|
||||
validates :callback_url, presence: true
|
||||
validates :callback_url, uniqueness: { scope: :account_id }
|
||||
|
||||
scope :confirmed, -> { where(confirmed: true) }
|
||||
scope :future_expiration, -> { where(arel_table[:expires_at].gt(Time.now.utc)) }
|
||||
scope :expired, -> { where(arel_table[:expires_at].lt(Time.now.utc)) }
|
||||
scope :active, -> { confirmed.future_expiration }
|
||||
|
||||
def lease_seconds=(value)
|
||||
self.expires_at = future_expiration(value)
|
||||
end
|
||||
|
||||
def lease_seconds
|
||||
(expires_at - Time.now.utc).to_i
|
||||
end
|
||||
|
||||
def expired?
|
||||
Time.now.utc > expires_at
|
||||
end
|
||||
|
||||
before_validation :set_min_expiration
|
||||
|
||||
private
|
||||
|
||||
def future_expiration(value)
|
||||
Time.now.utc + future_offset(value).seconds
|
||||
end
|
||||
|
||||
def future_offset(seconds)
|
||||
[
|
||||
[MIN_EXPIRATION, seconds.to_i].max,
|
||||
MAX_EXPIRATION,
|
||||
].min
|
||||
end
|
||||
|
||||
def set_min_expiration
|
||||
self.lease_seconds = 0 unless expires_at
|
||||
end
|
||||
end
|
||||
@@ -14,7 +14,6 @@ class Tag < ApplicationRecord
|
||||
has_and_belongs_to_many :accounts
|
||||
has_and_belongs_to_many :sample_accounts, -> { searchable.discoverable.popular.limit(3) }, class_name: 'Account'
|
||||
|
||||
has_many :featured_tags, dependent: :destroy, inverse_of: :tag
|
||||
has_one :account_tag_stat, dependent: :destroy
|
||||
|
||||
HASHTAG_NAME_RE = '[[:word:]_]*[[:alpha:]_·][[:word:]_]*'
|
||||
|
||||
@@ -1,65 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
class TrendingTags
|
||||
KEY = 'trending_tags'
|
||||
EXPIRE_HISTORY_AFTER = 7.days.seconds
|
||||
EXPIRE_TRENDS_AFTER = 1.day.seconds
|
||||
THRESHOLD = 5
|
||||
|
||||
class << self
|
||||
include Redisable
|
||||
|
||||
def record_use!(tag, account, at_time = Time.now.utc)
|
||||
return if disallowed_hashtags.include?(tag.name) || account.silenced? || account.bot?
|
||||
|
||||
increment_historical_use!(tag.id, at_time)
|
||||
increment_unique_use!(tag.id, account.id, at_time)
|
||||
increment_vote!(tag.id, at_time)
|
||||
end
|
||||
|
||||
def get(limit)
|
||||
key = "#{KEY}:#{Time.now.utc.beginning_of_day.to_i}"
|
||||
tag_ids = redis.zrevrange(key, 0, limit - 1).map(&:to_i)
|
||||
tags = Tag.where(id: tag_ids).to_a.each_with_object({}) { |tag, h| h[tag.id] = tag }
|
||||
tag_ids.map { |tag_id| tags[tag_id] }.compact
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def increment_historical_use!(tag_id, at_time)
|
||||
key = "activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}"
|
||||
redis.incrby(key, 1)
|
||||
redis.expire(key, EXPIRE_HISTORY_AFTER)
|
||||
end
|
||||
|
||||
def increment_unique_use!(tag_id, account_id, at_time)
|
||||
key = "activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}:accounts"
|
||||
redis.pfadd(key, account_id)
|
||||
redis.expire(key, EXPIRE_HISTORY_AFTER)
|
||||
end
|
||||
|
||||
def increment_vote!(tag_id, at_time)
|
||||
key = "#{KEY}:#{at_time.beginning_of_day.to_i}"
|
||||
expected = redis.pfcount("activity:tags:#{tag_id}:#{(at_time - 1.day).beginning_of_day.to_i}:accounts").to_f
|
||||
expected = 1.0 if expected.zero?
|
||||
observed = redis.pfcount("activity:tags:#{tag_id}:#{at_time.beginning_of_day.to_i}:accounts").to_f
|
||||
|
||||
if expected > observed || observed < THRESHOLD
|
||||
redis.zrem(key, tag_id.to_s)
|
||||
else
|
||||
score = ((observed - expected)**2) / expected
|
||||
redis.zadd(key, score, tag_id.to_s)
|
||||
end
|
||||
|
||||
redis.expire(key, EXPIRE_TRENDS_AFTER)
|
||||
end
|
||||
|
||||
def disallowed_hashtags
|
||||
return @disallowed_hashtags if defined?(@disallowed_hashtags)
|
||||
|
||||
@disallowed_hashtags = Setting.disallowed_hashtags.nil? ? [] : Setting.disallowed_hashtags
|
||||
@disallowed_hashtags = @disallowed_hashtags.split(' ') if @disallowed_hashtags.is_a? String
|
||||
@disallowed_hashtags = @disallowed_hashtags.map(&:downcase)
|
||||
end
|
||||
end
|
||||
end
|
||||
@@ -33,7 +33,6 @@
|
||||
# account_id :bigint(8) not null
|
||||
# disabled :boolean default(FALSE), not null
|
||||
# moderator :boolean default(FALSE), not null
|
||||
# invite_id :bigint(8)
|
||||
# remember_token :string
|
||||
# chosen_languages :string is an Array
|
||||
# created_by_application_id :bigint(8)
|
||||
@@ -43,6 +42,8 @@
|
||||
#
|
||||
|
||||
class User < ApplicationRecord
|
||||
self.ignored_columns = ["{:column=>:invite}_id"]
|
||||
|
||||
include Settings::Extend
|
||||
include UserRoles
|
||||
|
||||
@@ -69,24 +70,18 @@ class User < ApplicationRecord
|
||||
include LdapAuthenticable
|
||||
|
||||
belongs_to :account, inverse_of: :user
|
||||
belongs_to :invite, counter_cache: :uses, optional: true
|
||||
belongs_to :created_by_application, class_name: 'Doorkeeper::Application', optional: true
|
||||
accepts_nested_attributes_for :account
|
||||
|
||||
has_many :applications, class_name: 'Doorkeeper::Application', as: :owner
|
||||
has_many :backups, inverse_of: :user
|
||||
|
||||
has_one :invite_request, class_name: 'UserInviteRequest', inverse_of: :user, dependent: :destroy
|
||||
accepts_nested_attributes_for :invite_request, reject_if: ->(attributes) { attributes['text'].blank? }
|
||||
|
||||
validates :locale, inclusion: I18n.available_locales.map(&:to_s), if: :locale?
|
||||
validates :unique_email, uniqueness: true, on: :create
|
||||
validates_with BlacklistedEmailValidator, on: :create
|
||||
validates_with EmailMxValidator, if: :validate_email_dns?
|
||||
validates :agreement, acceptance: { allow_nil: false, accept: [true, 'true', '1'] }, on: :create
|
||||
|
||||
scope :recent, -> { order(id: :desc) }
|
||||
scope :pending, -> { where(approved: false) }
|
||||
scope :approved, -> { where(approved: true) }
|
||||
scope :confirmed, -> { where.not(confirmed_at: nil) }
|
||||
scope :enabled, -> { where(disabled: false) }
|
||||
@@ -96,7 +91,6 @@ class User < ApplicationRecord
|
||||
scope :emailable, -> { confirmed.enabled.joins(:account).merge(Account.searchable) }
|
||||
|
||||
before_validation :sanitize_languages
|
||||
before_validation :set_unique_email
|
||||
before_create :set_approved
|
||||
after_create :prepare_new_user!
|
||||
|
||||
@@ -109,24 +103,15 @@ class User < ApplicationRecord
|
||||
|
||||
delegate :auto_play_gif, :default_sensitive, :unfollow_modal, :boost_modal, :delete_modal,
|
||||
:noindex, :theme, :display_media, :hide_network,
|
||||
:expand_spoilers, :default_language, :aggregate_reblogs, :show_application,
|
||||
:expand_spoilers, :default_language, :aggregate_reblogs,
|
||||
:group_in_home_feed, to: :settings, prefix: :setting, allow_nil: false
|
||||
|
||||
attr_reader :invite_code
|
||||
attr_writer :external
|
||||
|
||||
def confirmed?
|
||||
confirmed_at.present?
|
||||
end
|
||||
|
||||
def invited?
|
||||
invite_id.present?
|
||||
end
|
||||
|
||||
def valid_invitation?
|
||||
invite_id.present? && invite.valid_for_use?
|
||||
end
|
||||
|
||||
def disable!
|
||||
update!(disabled: true,
|
||||
last_sign_in_at: current_sign_in_at,
|
||||
@@ -138,8 +123,8 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def confirm
|
||||
new_user = !confirmed?
|
||||
self.approved = true if open_registrations?
|
||||
new_user = !confirmed?
|
||||
self.approved = true
|
||||
|
||||
super
|
||||
|
||||
@@ -149,8 +134,8 @@ class User < ApplicationRecord
|
||||
end
|
||||
|
||||
def confirm!
|
||||
new_user = !confirmed?
|
||||
self.approved = true if open_registrations?
|
||||
new_user = !confirmed?
|
||||
self.approved = true
|
||||
|
||||
skip_confirmation!
|
||||
save!
|
||||
@@ -158,18 +143,10 @@ class User < ApplicationRecord
|
||||
# prepare_new_user! if new_user && approved?
|
||||
end
|
||||
|
||||
def pending?
|
||||
!approved?
|
||||
end
|
||||
|
||||
def active_for_authentication?
|
||||
super && approved?
|
||||
end
|
||||
|
||||
def inactive_message
|
||||
!approved? ? :pending : super
|
||||
end
|
||||
|
||||
def approve!
|
||||
return if approved?
|
||||
|
||||
@@ -200,10 +177,6 @@ class User < ApplicationRecord
|
||||
settings.notification_emails['report']
|
||||
end
|
||||
|
||||
def allows_pending_account_emails?
|
||||
settings.notification_emails['pending_account']
|
||||
end
|
||||
|
||||
def hides_network?
|
||||
@hides_network ||= settings.hide_network
|
||||
end
|
||||
@@ -212,10 +185,6 @@ class User < ApplicationRecord
|
||||
@aggregates_reblogs ||= settings.aggregate_reblogs
|
||||
end
|
||||
|
||||
def shows_application?
|
||||
@shows_application ||= settings.show_application
|
||||
end
|
||||
|
||||
def allows_group_in_home_feed?
|
||||
settings.group_in_home_feed
|
||||
end
|
||||
@@ -249,11 +218,6 @@ class User < ApplicationRecord
|
||||
session.web_push_subscription.nil? ? nil : session.web_push_subscription
|
||||
end
|
||||
|
||||
def invite_code=(code)
|
||||
self.invite = Invite.find_by(code: code) if code.present?
|
||||
@invite_code = code
|
||||
end
|
||||
|
||||
def challenge
|
||||
#
|
||||
end
|
||||
@@ -294,11 +258,7 @@ class User < ApplicationRecord
|
||||
private
|
||||
|
||||
def set_approved
|
||||
self.approved = open_registrations? || valid_invitation? || external?
|
||||
end
|
||||
|
||||
def open_registrations?
|
||||
Setting.registrations_mode == 'open'
|
||||
self.approved = true
|
||||
end
|
||||
|
||||
def external?
|
||||
@@ -345,12 +305,4 @@ class User < ApplicationRecord
|
||||
end
|
||||
end
|
||||
|
||||
def set_unique_email
|
||||
user, domain = self.email.split('@')
|
||||
user = user.split('+').first
|
||||
if ((domain == 'gmail.com') || (domain == 'hotmail.com'))
|
||||
user = user.gsub('.', '')
|
||||
end
|
||||
self.unique_email = "#{user}@#{domain}"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -1,17 +0,0 @@
|
||||
# frozen_string_literal: true
|
||||
|
||||
# == Schema Information
|
||||
#
|
||||
# Table name: user_invite_requests
|
||||
#
|
||||
# id :bigint(8) not null, primary key
|
||||
# user_id :bigint(8)
|
||||
# text :text
|
||||
# created_at :datetime not null
|
||||
# updated_at :datetime not null
|
||||
#
|
||||
|
||||
class UserInviteRequest < ApplicationRecord
|
||||
belongs_to :user, inverse_of: :invite_request
|
||||
validates :text, presence: true, length: { maximum: 420 }
|
||||
end
|
||||
Reference in New Issue
Block a user