commit: fa7868675d0952f8e4e1aa2f6b77586bb56de2c1
parent: 10eb47a33e2f0c7f0eba5302319c4321db41294c
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Wed, 24 Feb 2016 12:57:29 +0100
Send Salmon interactions
Diffstat:
15 files changed, 118 insertions(+), 25 deletions(-)
diff --git a/Gemfile b/Gemfile
@@ -19,6 +19,7 @@ gem 'grape'
gem 'grape-route-helpers'
gem 'grape-entity'
gem 'hashie-forbidden_attributes'
+gem 'paranoia', '~> 2.0'
gem 'http'
gem 'addressable'
diff --git a/Gemfile.lock b/Gemfile.lock
@@ -152,6 +152,8 @@ GEM
addressable (~> 2.4)
http (~> 1.0)
nokogiri (~> 1.6)
+ paranoia (2.1.5)
+ activerecord (~> 4.0)
parser (2.3.0.6)
ast (~> 2.2)
pg (0.18.4)
@@ -305,6 +307,7 @@ DEPENDENCIES
nokogiri
nyan-cat-formatter
ostatus2
+ paranoia (~> 2.0)
pg
pry-rails
puma
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
@@ -2,7 +2,7 @@ class Favourite < ActiveRecord::Base
belongs_to :account, 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
:favorite
diff --git a/app/models/follow.rb b/app/models/follow.rb
@@ -2,13 +2,13 @@ class Follow < ActiveRecord::Base
belongs_to :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_id, uniqueness: { scope: :target_account_id }
def verb
- :follow
+ self.destroyed? ? :unfollow : :follow
end
def target
@@ -20,7 +20,7 @@ class Follow < ActiveRecord::Base
end
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
def title
diff --git a/app/models/status.rb b/app/models/status.rb
@@ -4,8 +4,11 @@ class Status < ActiveRecord::Base
belongs_to :thread, foreign_key: 'in_reply_to_id', class_name: 'Status'
belongs_to :reblog, foreign_key: 'reblog_of_id', class_name: 'Status'
- has_one :stream_entry, as: :activity
- has_many :favourites, inverse_of: :status
+ has_one :stream_entry, as: :activity, dependent: :destroy
+
+ 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 :uri, uniqueness: true, unless: 'local?'
diff --git a/app/services/base_service.rb b/app/services/base_service.rb
@@ -0,0 +1,3 @@
+class BaseService
+ include ApplicationHelper
+end
diff --git a/app/services/fetch_entry_service.rb b/app/services/fetch_entry_service.rb
@@ -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
diff --git a/app/services/fetch_feed_service.rb b/app/services/fetch_feed_service.rb
@@ -1,4 +1,6 @@
-class FetchFeedService
+class FetchFeedService < BaseService
+ # Fetch an account's feed and process it
+ # @param [Account] account
def call(account)
process_service.(http_client.get(account.remote_url), account)
end
@@ -6,7 +8,7 @@ class FetchFeedService
private
def process_service
- ProcessFeedService.new
+ @process_service ||= ProcessFeedService.new
end
def http_client
diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb
@@ -1,6 +1,10 @@
-class FollowRemoteAccountService
- include ApplicationHelper
-
+class FollowRemoteAccountService < BaseService
+ # 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)
username, domain = uri.split('@')
account = Account.where(username: username, domain: domain).first
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
@@ -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)
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
private
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
diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb
@@ -1,6 +1,7 @@
-class ProcessFeedService
- include ApplicationHelper
-
+class ProcessFeedService < BaseService
+ # Create local statuses from an Atom feed
+ # @param [String] body Atom feed
+ # @param [Account] account Account this feed belongs to
def call(body, account)
xml = Nokogiri::XML(body)
@@ -105,6 +106,6 @@ class ProcessFeedService
end
def follow_remote_account_service
- FollowRemoteAccountService.new
+ @follow_remote_account_service ||= FollowRemoteAccountService.new
end
end
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
@@ -1,6 +1,7 @@
-class ProcessInteractionService
- include ApplicationHelper
-
+class ProcessInteractionService < BaseService
+ # 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)
body = salmon.unpack(envelope)
xml = Nokogiri::XML(body)
@@ -75,14 +76,14 @@ class ProcessInteractionService
end
def salmon
- OStatus2::Salmon.new
+ @salmon ||= OStatus2::Salmon.new
end
def follow_remote_account_service
- FollowRemoteAccountService.new
+ @follow_remote_account_service ||= FollowRemoteAccountService.new
end
def process_feed_service
- ProcessFeedService.new
+ @process_feed_service ||= ProcessFeedService.new
end
end
diff --git a/app/services/send_interaction_service.rb b/app/services/send_interaction_service.rb
@@ -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
diff --git a/app/services/setup_local_account_service.rb b/app/services/setup_local_account_service.rb
@@ -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)
user.build_account
diff --git a/app/services/unfollow_service.rb b/app/services/unfollow_service.rb
@@ -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