Skip to content
Snippets Groups Projects
account.rb 6.46 KiB
Newer Older
  • Learn to ignore specific revisions
  • Eugen Rochko's avatar
    Eugen Rochko committed
    class Account < ApplicationRecord
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      MENTION_RE = /(?:^|[\s\.>*+])@([a-z0-9_]+(?:@[a-z0-9\.\-]+)?)/i
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      IMAGE_MIME_TYPES = ['image/jpeg', 'image/png', 'image/gif'].freeze
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      # Local users
      has_one :user, inverse_of: :account
    
      validates :username, presence: true, format: { with: /\A[a-z0-9_]+\z/i, message: 'only letters, numbers and underscores' }, uniqueness: { scope: :domain, case_sensitive: false }, length: { maximum: 30 }, if: 'local?'
    
      validates :username, presence: true, uniqueness: { scope: :domain, case_sensitive: true }, unless: 'local?'
    
    Eugen Rochko's avatar
    Eugen Rochko committed
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      has_attached_file :avatar, styles: { large: '300x300#', medium: '96x96#', small: '48x48#' }
    
      validates_attachment_content_type :avatar, content_type: IMAGE_MIME_TYPES
    
      validates_attachment_size :avatar, less_than: 2.megabytes
    
      # Header upload
      has_attached_file :header, styles: { medium: '700x335#' }
    
      validates_attachment_content_type :header, content_type: IMAGE_MIME_TYPES
    
      validates_attachment_size :header, less_than: 2.megabytes
    
      # Local user profile validations
      validates :display_name, length: { maximum: 30 }, if: 'local?'
      validates :note, length: { maximum: 124 }, if: 'local?'
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      # Timelines
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      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
    
    Eugen Rochko's avatar
    Eugen Rochko committed
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      # Follow relations
      has_many :active_relationships,  class_name: 'Follow', foreign_key: 'account_id',        dependent: :destroy
      has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_account_id', dependent: :destroy
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      has_many :following, -> { order('follows.id desc') }, through: :active_relationships,  source: :target_account
      has_many :followers, -> { order('follows.id desc') }, through: :passive_relationships, source: :account
    
    
      # Block relationships
      has_many :block_relationships, class_name: 'Block', foreign_key: 'account_id', dependent: :destroy
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      has_many :blocking, -> { order('blocks.id desc') }, through: :block_relationships, source: :target_account
    
    Eugen Rochko's avatar
    Eugen Rochko committed
    
    
      has_many :media_attachments, dependent: :destroy
    
    
      scope :remote, -> { where.not(domain: nil) }
      scope :local, -> { where(domain: nil) }
      scope :without_followers, -> { where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) = 0') }
      scope :with_followers, -> { where('(select count(f.id) from follows as f where f.target_account_id = accounts.id) > 0') }
      scope :expiring, -> (time) { where(subscription_expires_at: nil).or(where('subscription_expires_at < ?', time)).remote.with_followers }
    
    
      scope :with_counters, -> { select('accounts.*, (select count(f.id) from follows as f where f.target_account_id = accounts.id) as followers_count, (select count(f.id) from follows as f where f.account_id = accounts.id) as following_count, (select count(s.id) from statuses as s where s.account_id = accounts.id) as statuses_count') }
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      def follow!(other_account)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        active_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      end
    
    
      def block!(other_account)
        block_relationships.where(target_account: other_account).first_or_create!(target_account: other_account)
      end
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      def unfollow!(other_account)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        follow = active_relationships.find_by(target_account: other_account)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      end
    
    
      def unblock!(other_account)
        block = block_relationships.find_by(target_account: other_account)
        block.destroy unless block.nil?
      end
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      def following?(other_account)
        following.include?(other_account)
      end
    
    
      def blocking?(other_account)
        blocking.include?(other_account)
      end
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      def local?
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        domain.nil?
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      end
    
    
      def acct
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        local? ? username : "#{username}@#{domain}"
    
      end
    
      def subscribed?
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        !subscription_expires_at.nil?
    
      def favourited?(status)
    
        (status.reblog? ? status.reblog : status).favourites.where(account: self).count > 0
    
      end
    
      def reblogged?(status)
    
        (status.reblog? ? status.reblog : status).reblogs.where(account: self).count > 0
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      def keypair
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        private_key.nil? ? OpenSSL::PKey::RSA.new(public_key) : OpenSSL::PKey::RSA.new(private_key)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      end
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      def subscription(webhook_url)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        OStatus2::Subscription.new(remote_url, secret: secret, lease_seconds: 86_400 * 30, webhook: webhook_url, hub: hub_url)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      end
    
        return unless local? && !Rails.env.development?
    
        OStatus2::Publication.new(atom_url, hubs).publish
      end
    
    
      def avatar_remote_url=(url)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        self.avatar = URI.parse(url) unless self[:avatar_remote_url] == url
    
        self[:avatar_remote_url] = url
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        username
    
      def common_followers_with(other_account)
        results  = Neography::Rest.new.execute_query('MATCH (a {account_id: {a_id}})-[:follows]->(b)-[:follows]->(c {account_id: {c_id}}) RETURN b.account_id', a_id: id, c_id: other_account.id)
        ids      = results['data'].map(&:first)
    
        accounts = Account.where(id: ids).with_counters.limit(20).map { |a| [a.id, a] }.to_h
    
        ids.map { |id| accounts[id] }.compact
      rescue Neography::NeographyError, Excon::Error::Socket
        []
      end
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        find_remote!(username, nil)
    
      end
    
      def self.find_remote!(username, domain)
    
        where(arel_table[:username].matches(username)).where(domain.nil? ? { domain: nil } : arel_table[:domain].matches(domain)).take!
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        find_local!(username)
    
      def self.find_remote(username, domain)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
        find_remote!(username, domain)
    
      rescue ActiveRecord::RecordNotFound
        nil
      end
    
    
      def self.following_map(target_account_ids, account_id)
        Follow.where(target_account_id: target_account_ids).where(account_id: account_id).map { |f| [f.target_account_id, true] }.to_h
      end
    
      def self.followed_by_map(target_account_ids, account_id)
        Follow.where(account_id: target_account_ids).where(target_account_id: account_id).map { |f| [f.account_id, true] }.to_h
      end
    
    
      def self.blocking_map(target_account_ids, account_id)
        Block.where(target_account_id: target_account_ids).where(account_id: account_id).map { |b| [b.target_account_id, true] }.to_h
      end
    
    
    Eugen Rochko's avatar
    Eugen Rochko committed
      before_create do
        if local?
    
          keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 1024 : 2048)
    
    Eugen Rochko's avatar
    Eugen Rochko committed
          self.private_key = keypair.to_pem
          self.public_key  = keypair.public_key.to_pem
        end
      end
    
    Eugen Rochko's avatar
    Eugen Rochko committed
    end