Send Salmon interactions
This commit is contained in:
		
							parent
							
								
									10eb47a33e
								
							
						
					
					
						commit
						fa7868675d
					
				
					 15 changed files with 118 additions and 25 deletions
				
			
		
							
								
								
									
										1
									
								
								Gemfile
									
										
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
										
									
									
									
								
							| 
						 | 
					@ -19,6 +19,7 @@ gem 'grape'
 | 
				
			||||||
gem 'grape-route-helpers'
 | 
					gem 'grape-route-helpers'
 | 
				
			||||||
gem 'grape-entity'
 | 
					gem 'grape-entity'
 | 
				
			||||||
gem 'hashie-forbidden_attributes'
 | 
					gem 'hashie-forbidden_attributes'
 | 
				
			||||||
 | 
					gem 'paranoia', '~> 2.0'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
gem 'http'
 | 
					gem 'http'
 | 
				
			||||||
gem 'addressable'
 | 
					gem 'addressable'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -152,6 +152,8 @@ GEM
 | 
				
			||||||
      addressable (~> 2.4)
 | 
					      addressable (~> 2.4)
 | 
				
			||||||
      http (~> 1.0)
 | 
					      http (~> 1.0)
 | 
				
			||||||
      nokogiri (~> 1.6)
 | 
					      nokogiri (~> 1.6)
 | 
				
			||||||
 | 
					    paranoia (2.1.5)
 | 
				
			||||||
 | 
					      activerecord (~> 4.0)
 | 
				
			||||||
    parser (2.3.0.6)
 | 
					    parser (2.3.0.6)
 | 
				
			||||||
      ast (~> 2.2)
 | 
					      ast (~> 2.2)
 | 
				
			||||||
    pg (0.18.4)
 | 
					    pg (0.18.4)
 | 
				
			||||||
| 
						 | 
					@ -305,6 +307,7 @@ DEPENDENCIES
 | 
				
			||||||
  nokogiri
 | 
					  nokogiri
 | 
				
			||||||
  nyan-cat-formatter
 | 
					  nyan-cat-formatter
 | 
				
			||||||
  ostatus2
 | 
					  ostatus2
 | 
				
			||||||
 | 
					  paranoia (~> 2.0)
 | 
				
			||||||
  pg
 | 
					  pg
 | 
				
			||||||
  pry-rails
 | 
					  pry-rails
 | 
				
			||||||
  puma
 | 
					  puma
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,7 +2,7 @@ class Favourite < ActiveRecord::Base
 | 
				
			||||||
  belongs_to :account, inverse_of: :favourites
 | 
					  belongs_to :account, inverse_of: :favourites
 | 
				
			||||||
  belongs_to :status,  inverse_of: :favourites
 | 
					  belongs_to :status,  inverse_of: :favourites
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_one :stream_entry, as: :activity
 | 
					  has_one :stream_entry, as: :activity, dependent: :destroy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def verb
 | 
					  def verb
 | 
				
			||||||
    :favorite
 | 
					    :favorite
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -2,13 +2,13 @@ class Follow < ActiveRecord::Base
 | 
				
			||||||
  belongs_to :account
 | 
					  belongs_to :account
 | 
				
			||||||
  belongs_to :target_account, class_name: 'Account'
 | 
					  belongs_to :target_account, class_name: 'Account'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_one :stream_entry, as: :activity
 | 
					  has_one :stream_entry, as: :activity, dependent: :destroy
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :account, :target_account, presence: true
 | 
					  validates :account, :target_account, presence: true
 | 
				
			||||||
  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
					  validates :account_id, uniqueness: { scope: :target_account_id }
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def verb
 | 
					  def verb
 | 
				
			||||||
    :follow
 | 
					    self.destroyed? ? :unfollow : :follow
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def target
 | 
					  def target
 | 
				
			||||||
| 
						 | 
					@ -20,7 +20,7 @@ class Follow < ActiveRecord::Base
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def content
 | 
					  def content
 | 
				
			||||||
    "#{self.account.acct} started following #{self.target_account.acct}"
 | 
					    self.destroyed? ? "#{self.account.acct} is no longer following #{self.target_account.acct}" : "#{self.account.acct} started following #{self.target_account.acct}"
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def title
 | 
					  def title
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -4,8 +4,11 @@ class Status < ActiveRecord::Base
 | 
				
			||||||
  belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
 | 
					  belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
 | 
				
			||||||
  belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
 | 
					  belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  has_one :stream_entry, as: :activity
 | 
					  has_one :stream_entry, as: :activity, dependent: :destroy
 | 
				
			||||||
  has_many :favourites, inverse_of: :status
 | 
					
 | 
				
			||||||
 | 
					  has_many :favourites, inverse_of: :status, dependent: :destroy
 | 
				
			||||||
 | 
					  has_many :reblogs, foreign_key: 'reblog_of_id', class_name: 'Status'
 | 
				
			||||||
 | 
					  has_many :replies, foreign_key: 'in_reply_to_id', class_name: 'Status'
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  validates :account, presence: true
 | 
					  validates :account, presence: true
 | 
				
			||||||
  validates :uri, uniqueness: true, unless: 'local?'
 | 
					  validates :uri, uniqueness: true, unless: 'local?'
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										3
									
								
								app/services/base_service.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/services/base_service.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,3 @@
 | 
				
			||||||
 | 
					class BaseService
 | 
				
			||||||
 | 
					  include ApplicationHelper
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
							
								
								
									
										16
									
								
								app/services/fetch_entry_service.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/services/fetch_entry_service.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,16 @@
 | 
				
			||||||
 | 
					class FetchEntryService < BaseService
 | 
				
			||||||
 | 
					  # Knowing nothing but the URL of a remote status, create a local representation of it and return it
 | 
				
			||||||
 | 
					  # @param [String] url Atom URL
 | 
				
			||||||
 | 
					  # @return [Status]
 | 
				
			||||||
 | 
					  def call(url)
 | 
				
			||||||
 | 
					    body = http_client.get(url)
 | 
				
			||||||
 | 
					    xml  = Nokogiri::XML(body)
 | 
				
			||||||
 | 
					    # todo
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def http_client
 | 
				
			||||||
 | 
					    HTTP
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,6 @@
 | 
				
			||||||
class FetchFeedService
 | 
					class FetchFeedService < BaseService
 | 
				
			||||||
 | 
					  # Fetch an account's feed and process it
 | 
				
			||||||
 | 
					  # @param [Account] account
 | 
				
			||||||
  def call(account)
 | 
					  def call(account)
 | 
				
			||||||
    process_service.(http_client.get(account.remote_url), account)
 | 
					    process_service.(http_client.get(account.remote_url), account)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
| 
						 | 
					@ -6,7 +8,7 @@ class FetchFeedService
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def process_service
 | 
					  def process_service
 | 
				
			||||||
    ProcessFeedService.new
 | 
					    @process_service ||= ProcessFeedService.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def http_client
 | 
					  def http_client
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,10 @@
 | 
				
			||||||
class FollowRemoteAccountService
 | 
					class FollowRemoteAccountService < BaseService
 | 
				
			||||||
  include ApplicationHelper
 | 
					  # Find or create a local account for a remote user.
 | 
				
			||||||
 | 
					  # When creating, look up the user's webfinger and fetch all
 | 
				
			||||||
 | 
					  # important information from their feed
 | 
				
			||||||
 | 
					  # @param [String] uri User URI in the form of username@domain
 | 
				
			||||||
 | 
					  # @param [Boolean] subscribe Whether to initiate a PubSubHubbub subscription
 | 
				
			||||||
 | 
					  # @return [Account]
 | 
				
			||||||
  def call(uri, subscribe = true)
 | 
					  def call(uri, subscribe = true)
 | 
				
			||||||
    username, domain = uri.split('@')
 | 
					    username, domain = uri.split('@')
 | 
				
			||||||
    account = Account.where(username: username, domain: domain).first
 | 
					    account = Account.where(username: username, domain: domain).first
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,12 +1,23 @@
 | 
				
			||||||
class FollowService
 | 
					class FollowService < BaseService
 | 
				
			||||||
 | 
					  # Follow a remote user, notify remote user about the follow
 | 
				
			||||||
 | 
					  # @param [Account] source_account From which to follow
 | 
				
			||||||
 | 
					  # @param [String] uri User URI to follow in the form of username@domain
 | 
				
			||||||
  def call(source_account, uri)
 | 
					  def call(source_account, uri)
 | 
				
			||||||
    target_account = follow_remote_account_service.(uri)
 | 
					    target_account = follow_remote_account_service.(uri)
 | 
				
			||||||
    source_account.follow!(target_account) unless target_account.nil?
 | 
					
 | 
				
			||||||
 | 
					    return if target_account.nil?
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					    follow = source_account.follow!(target_account)
 | 
				
			||||||
 | 
					    send_interaction_service.(follow.stream_entry, target_account)
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  private
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def follow_remote_account_service
 | 
					  def follow_remote_account_service
 | 
				
			||||||
    FollowRemoteAccountService.new
 | 
					    @follow_remote_account_service ||= FollowRemoteAccountService.new
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send_interaction_service
 | 
				
			||||||
 | 
					    @send_interaction_service ||= SendInteractionService.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
class ProcessFeedService
 | 
					class ProcessFeedService < BaseService
 | 
				
			||||||
  include ApplicationHelper
 | 
					  # Create local statuses from an Atom feed
 | 
				
			||||||
 | 
					  # @param [String] body Atom feed
 | 
				
			||||||
 | 
					  # @param [Account] account Account this feed belongs to
 | 
				
			||||||
  def call(body, account)
 | 
					  def call(body, account)
 | 
				
			||||||
    xml = Nokogiri::XML(body)
 | 
					    xml = Nokogiri::XML(body)
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					@ -105,6 +106,6 @@ class ProcessFeedService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def follow_remote_account_service
 | 
					  def follow_remote_account_service
 | 
				
			||||||
    FollowRemoteAccountService.new
 | 
					    @follow_remote_account_service ||= FollowRemoteAccountService.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
| 
						 | 
					@ -1,6 +1,7 @@
 | 
				
			||||||
class ProcessInteractionService
 | 
					class ProcessInteractionService < BaseService
 | 
				
			||||||
  include ApplicationHelper
 | 
					  # Record locally the remote interaction with our user
 | 
				
			||||||
 | 
					  # @param [String] envelope Salmon envelope
 | 
				
			||||||
 | 
					  # @param [Account] target_account Account the Salmon was addressed to
 | 
				
			||||||
  def call(envelope, target_account)
 | 
					  def call(envelope, target_account)
 | 
				
			||||||
    body = salmon.unpack(envelope)
 | 
					    body = salmon.unpack(envelope)
 | 
				
			||||||
    xml  = Nokogiri::XML(body)
 | 
					    xml  = Nokogiri::XML(body)
 | 
				
			||||||
| 
						 | 
					@ -75,14 +76,14 @@ class ProcessInteractionService
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def salmon
 | 
					  def salmon
 | 
				
			||||||
    OStatus2::Salmon.new
 | 
					    @salmon ||= OStatus2::Salmon.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def follow_remote_account_service
 | 
					  def follow_remote_account_service
 | 
				
			||||||
    FollowRemoteAccountService.new
 | 
					    @follow_remote_account_service ||= FollowRemoteAccountService.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
  def process_feed_service
 | 
					  def process_feed_service
 | 
				
			||||||
    ProcessFeedService.new
 | 
					    @process_feed_service ||= ProcessFeedService.new
 | 
				
			||||||
  end
 | 
					  end
 | 
				
			||||||
end
 | 
					end
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										29
									
								
								app/services/send_interaction_service.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								app/services/send_interaction_service.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,29 @@
 | 
				
			||||||
 | 
					class SendInteractionService < BaseService
 | 
				
			||||||
 | 
					  include AtomHelper
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  # Send an Atom representation of an interaction to a remote Salmon endpoint
 | 
				
			||||||
 | 
					  # @param [StreamEntry] stream_entry
 | 
				
			||||||
 | 
					  # @param [Account] target_account
 | 
				
			||||||
 | 
					  def call(stream_entry, target_account)
 | 
				
			||||||
 | 
					    envelope = salmon.pack(entry_xml(stream_entry), target_account.keypair)
 | 
				
			||||||
 | 
					    salmon.post(target_account.salmon_url, envelope)
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def entry_xml(stream_entry)
 | 
				
			||||||
 | 
					    Nokogiri::XML::Builder.new do |xml|
 | 
				
			||||||
 | 
					      entry(xml, true) do
 | 
				
			||||||
 | 
					        author(xml) do
 | 
				
			||||||
 | 
					          include_author xml, stream_entry.account
 | 
				
			||||||
 | 
					        end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					        include_entry xml, stream_entry
 | 
				
			||||||
 | 
					      end
 | 
				
			||||||
 | 
					    end.to_xml
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def salmon
 | 
				
			||||||
 | 
					    @salmon ||= OStatus2::Salmon.new
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
| 
						 | 
					@ -1,4 +1,8 @@
 | 
				
			||||||
class SetupLocalAccountService
 | 
					class SetupLocalAccountService < BaseService
 | 
				
			||||||
 | 
					  # Setup an account for a new user instance by generating
 | 
				
			||||||
 | 
					  # an RSA key pair and a profile
 | 
				
			||||||
 | 
					  # @param [User] user Unsaved user instance
 | 
				
			||||||
 | 
					  # @param [String] username
 | 
				
			||||||
  def call(user, username)
 | 
					  def call(user, username)
 | 
				
			||||||
    user.build_account
 | 
					    user.build_account
 | 
				
			||||||
 | 
					
 | 
				
			||||||
| 
						 | 
					
 | 
				
			||||||
							
								
								
									
										15
									
								
								app/services/unfollow_service.rb
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								app/services/unfollow_service.rb
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
					@ -0,0 +1,15 @@
 | 
				
			||||||
 | 
					class UnfollowService < BaseService
 | 
				
			||||||
 | 
					  # Unfollow and notify the remote user
 | 
				
			||||||
 | 
					  # @param [Account] source_account Where to unfollow from
 | 
				
			||||||
 | 
					  # @param [Account] target_account Which to unfollow
 | 
				
			||||||
 | 
					  def call(source_account, target_account)
 | 
				
			||||||
 | 
					    follow = source_account.unfollow!(target_account)
 | 
				
			||||||
 | 
					    send_interaction_service.(follow.stream_entry, target_account) unless target_account.local?
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  private
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					  def send_interaction_service
 | 
				
			||||||
 | 
					    @send_interaction_service ||= SendInteractionService.new
 | 
				
			||||||
 | 
					  end
 | 
				
			||||||
 | 
					end
 | 
				
			||||||
		Loading…
	
	Add table
		
		Reference in a new issue