about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--Gemfile1
-rw-r--r--Gemfile.lock3
-rw-r--r--app/models/favourite.rb2
-rw-r--r--app/models/follow.rb6
-rw-r--r--app/models/status.rb7
-rw-r--r--app/services/base_service.rb3
-rw-r--r--app/services/fetch_entry_service.rb16
-rw-r--r--app/services/fetch_feed_service.rb6
-rw-r--r--app/services/follow_remote_account_service.rb10
-rw-r--r--app/services/follow_service.rb17
-rw-r--r--app/services/process_feed_service.rb9
-rw-r--r--app/services/process_interaction_service.rb13
-rw-r--r--app/services/send_interaction_service.rb29
-rw-r--r--app/services/setup_local_account_service.rb6
-rw-r--r--app/services/unfollow_service.rb15
15 files changed, 118 insertions, 25 deletions
diff --git a/Gemfile b/Gemfile
index f0a77f7b6..ef4273993 100644
--- 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
index fad87d191..434d72f42 100644
--- 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
index 20260f46b..ad5380f43 100644
--- 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
index aa723d705..3521247e8 100644
--- 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
index 72bf1b790..be616dce6 100644
--- 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
new file mode 100644
index 000000000..0816b3503
--- /dev/null
+++ 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
new file mode 100644
index 000000000..c4a5460e9
--- /dev/null
+++ 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
index 059d65925..f18e9fc06 100644
--- 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
index bb55362a6..de2ca6e75 100644
--- 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
index ea868b544..623d52b74 100644
--- 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
index b7952035b..1aaf85d94 100644
--- 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
index ee04f01af..3a8332118 100644
--- 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
new file mode 100644
index 000000000..42b273ed6
--- /dev/null
+++ 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
index c40e51855..a5ef68996 100644
--- 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
new file mode 100644
index 000000000..039838d32
--- /dev/null
+++ 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