2016-11-15 17:56:29 +02:00
# frozen_string_literal: true
2016-08-17 18:56:23 +03:00
class Account < ApplicationRecord
2016-03-25 03:13:30 +02:00
include Targetable
2016-11-12 15:33:21 +02:00
include PgSearch
2016-03-25 03:13:30 +02:00
2016-11-07 18:35:25 +02:00
MENTION_RE = / (?:^|[^ \/ \ w])@([a-z0-9_]+(?:@[a-z0-9 \ . \ -]+)?) /i
2016-09-29 22:28:21 +03:00
IMAGE_MIME_TYPES = [ 'image/jpeg' , 'image/png' , 'image/gif' ] . freeze
2016-09-12 19:22:43 +03:00
2016-02-22 17:00:20 +02:00
# Local users
has_one :user , inverse_of : :account
2016-09-25 16:26:56 +03:00
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?'
2016-09-10 10:43:45 +03:00
validates :username , presence : true , uniqueness : { scope : :domain , case_sensitive : true } , unless : 'local?'
2016-02-22 17:00:20 +02:00
2016-02-28 01:02:59 +02:00
# Avatar upload
2016-09-08 19:23:59 +03:00
has_attached_file :avatar , styles : { large : '300x300#' , medium : '96x96#' , small : '48x48#' }
2016-09-12 19:22:43 +03:00
validates_attachment_content_type :avatar , content_type : IMAGE_MIME_TYPES
2016-09-10 10:43:45 +03:00
validates_attachment_size :avatar , less_than : 2 . megabytes
2016-02-28 01:02:59 +02:00
2016-03-12 21:47:22 +02:00
# Header upload
has_attached_file :header , styles : { medium : '700x335#' }
2016-09-12 19:22:43 +03:00
validates_attachment_content_type :header , content_type : IMAGE_MIME_TYPES
2016-09-10 10:43:45 +03:00
validates_attachment_size :header , less_than : 2 . megabytes
2016-03-12 21:47:22 +02:00
2016-03-16 12:18:09 +02:00
# Local user profile validations
validates :display_name , length : { maximum : 30 } , if : 'local?'
2016-11-07 02:14:12 +02:00
validates :note , length : { maximum : 160 } , if : 'local?'
2016-03-16 12:18:09 +02:00
2016-02-22 17:00:20 +02:00
# Timelines
2016-10-09 15:48:43 +03:00
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
2016-11-21 15:59:13 +02:00
has_many :notifications , inverse_of : :account , dependent : :destroy
2016-02-20 23:53:20 +02:00
2016-02-22 17:00:20 +02:00
# 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
2016-11-04 20:12:59 +02:00
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
2016-10-03 18:11:54 +03:00
# Block relationships
has_many :block_relationships , class_name : 'Block' , foreign_key : 'account_id' , dependent : :destroy
2016-11-04 20:12:59 +02:00
has_many :blocking , - > { order ( 'blocks.id desc' ) } , through : :block_relationships , source : :target_account
2016-02-22 17:00:20 +02:00
2016-09-05 18:46:36 +03:00
has_many :media_attachments , dependent : :destroy
2016-11-12 15:56:40 +02:00
pg_search_scope :search_for , against : { username : 'A' , domain : 'B' } , using : { tsearch : { prefix : true } }
2016-11-12 15:33:21 +02:00
2016-09-20 01:39:03 +03:00
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 }
2016-10-16 19:57:54 +03:00
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' ) }
2016-02-22 17:00:20 +02:00
def follow! ( other_account )
2016-09-29 22:28:21 +03:00
active_relationships . where ( target_account : other_account ) . first_or_create! ( target_account : other_account )
2016-02-22 17:00:20 +02:00
end
2016-10-03 18:16:58 +03:00
def block! ( other_account )
block_relationships . where ( target_account : other_account ) . first_or_create! ( target_account : other_account )
end
2016-02-22 17:00:20 +02:00
def unfollow! ( other_account )
2016-09-29 22:28:21 +03:00
follow = active_relationships . find_by ( target_account : other_account )
2016-03-16 22:23:40 +02:00
follow . destroy unless follow . nil?
2016-02-22 17:00:20 +02:00
end
2016-10-03 18:16:58 +03:00
def unblock! ( other_account )
block = block_relationships . find_by ( target_account : other_account )
block . destroy unless block . nil?
end
2016-02-22 17:00:20 +02:00
def following? ( other_account )
following . include? ( other_account )
end
2016-10-03 18:11:54 +03:00
def blocking? ( other_account )
blocking . include? ( other_account )
end
2016-02-22 17:00:20 +02:00
def local?
2016-09-29 22:28:21 +03:00
domain . nil?
2016-02-22 17:00:20 +02:00
end
2016-02-22 19:10:30 +02:00
def acct
2016-09-29 22:28:21 +03:00
local? ? username : " #{ username } @ #{ domain } "
2016-02-22 19:10:30 +02:00
end
def subscribed?
2016-09-29 22:28:21 +03:00
! subscription_expires_at . nil?
2016-02-22 19:10:30 +02:00
end
2016-03-07 13:42:33 +02:00
def favourited? ( status )
2016-11-15 17:56:29 +02:00
( status . reblog? ? status . reblog : status ) . favourites . where ( account : self ) . count . positive?
2016-03-07 13:42:33 +02:00
end
def reblogged? ( status )
2016-11-15 17:56:29 +02:00
( status . reblog? ? status . reblog : status ) . reblogs . where ( account : self ) . count . positive?
2016-03-07 13:42:33 +02:00
end
2016-02-22 17:00:20 +02:00
def keypair
2016-09-29 22:28:21 +03:00
private_key . nil? ? OpenSSL :: PKey :: RSA . new ( public_key ) : OpenSSL :: PKey :: RSA . new ( private_key )
2016-02-22 17:00:20 +02:00
end
2016-02-20 23:53:20 +02:00
def subscription ( webhook_url )
2016-09-29 22:28:21 +03:00
OStatus2 :: Subscription . new ( remote_url , secret : secret , lease_seconds : 86_400 * 30 , webhook : webhook_url , hub : hub_url )
2016-02-20 23:53:20 +02:00
end
2016-02-26 16:28:08 +02:00
2016-02-28 15:33:13 +02:00
def ping! ( atom_url , hubs )
2016-09-01 14:21:48 +03:00
return unless local? && ! Rails . env . development?
2016-02-28 15:33:13 +02:00
OStatus2 :: Publication . new ( atom_url , hubs ) . publish
end
2016-02-28 01:51:05 +02:00
def avatar_remote_url = ( url )
2016-09-29 22:28:21 +03:00
self . avatar = URI . parse ( url ) unless self [ :avatar_remote_url ] == url
2016-03-22 22:05:23 +02:00
self [ :avatar_remote_url ] = url
2016-11-15 17:56:29 +02:00
rescue OpenURI :: HTTPError = > e
Rails . logger . debug " Error fetching remote avatar: #{ e } "
2016-02-28 01:51:05 +02:00
end
2016-03-25 03:13:30 +02:00
def object_type
:person
end
2016-02-29 20:42:08 +02:00
def to_param
2016-09-29 22:28:21 +03:00
username
2016-02-29 20:42:08 +02:00
end
2016-10-29 02:29:19 +03:00
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 )
2016-10-30 16:14:07 +02:00
accounts = Account . where ( id : ids ) . with_counters . limit ( 20 ) . map { | a | [ a . id , a ] } . to_h
2016-10-29 02:29:19 +03:00
ids . map { | id | accounts [ id ] } . compact
rescue Neography :: NeographyError , Excon :: Error :: Socket
[ ]
end
2016-11-09 18:48:44 +02:00
class << self
def find_local! ( username )
find_remote! ( username , nil )
end
2016-09-04 22:06:04 +03:00
2016-11-09 18:48:44 +02:00
def find_remote! ( username , domain )
2016-11-13 12:27:13 +02:00
where ( arel_table [ :username ] . matches ( username . gsub ( / [%_] / , '\\\\\0' ) ) ) . where ( domain . nil? ? { domain : nil } : arel_table [ :domain ] . matches ( domain . gsub ( / [%_] / , '\\\\\0' ) ) ) . take!
2016-11-09 18:48:44 +02:00
end
2016-03-16 19:29:52 +02:00
2016-11-09 18:48:44 +02:00
def find_local ( username )
find_local! ( username )
rescue ActiveRecord :: RecordNotFound
nil
end
2016-03-19 00:23:19 +02:00
2016-11-09 18:48:44 +02:00
def find_remote ( username , domain )
find_remote! ( username , domain )
rescue ActiveRecord :: RecordNotFound
nil
end
2016-09-04 22:06:04 +03:00
2016-11-09 18:48:44 +02:00
def 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
2016-09-21 23:07:18 +03:00
2016-11-09 18:48:44 +02:00
def 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
2016-09-21 23:07:18 +03:00
2016-11-09 18:48:44 +02:00
def 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
2016-10-03 18:16:58 +03:00
end
2016-02-26 16:28:08 +02:00
before_create do
if local?
2016-02-28 22:22:56 +02:00
keypair = OpenSSL :: PKey :: RSA . new ( Rails . env . test? ? 1024 : 2048 )
2016-02-26 16:28:08 +02:00
self . private_key = keypair . to_pem
self . public_key = keypair . public_key . to_pem
end
end
2016-02-20 23:53:20 +02:00
end