about summary refs log tree commit diff
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-02-23 19:17:37 +0100
committerEugen Rochko <eugen@zeonfederated.com>2016-02-23 19:17:37 +0100
commitfa33750105389110a3395ca19167f789d21a149e (patch)
tree4b121a067ad46be5189301a2e3e3b2bcef8d6426
parent3b0bc18db928c455186273d9b9aa5b96d91e035e (diff)
Adding reblogs, favourites, improving atom generation
-rw-r--r--app/controllers/profile_controller.rb3
-rw-r--r--app/helpers/atom_helper.rb81
-rw-r--r--app/models/account.rb3
-rw-r--r--app/models/favourite.rb38
-rw-r--r--app/models/follow.rb15
-rw-r--r--app/models/status.rb38
-rw-r--r--app/models/stream_entry.rb16
-rw-r--r--app/services/follow_remote_account_service.rb1
-rw-r--r--app/services/process_interaction_service.rb6
-rw-r--r--app/views/atom/entry.xml.ruby34
-rw-r--r--app/views/atom/user_stream.xml.ruby31
-rw-r--r--config/routes.rb1
-rw-r--r--db/migrate/20160223162837_add_metadata_to_statuses.rb6
-rw-r--r--db/migrate/20160223164502_make_uris_nullable_in_statuses.rb5
-rw-r--r--db/migrate/20160223165723_add_url_to_statuses.rb5
-rw-r--r--db/migrate/20160223165855_add_url_to_accounts.rb5
-rw-r--r--db/migrate/20160223171800_create_favourites.rb12
-rw-r--r--db/schema.rb25
-rw-r--r--spec/models/favourite_spec.rb5
19 files changed, 251 insertions, 79 deletions
diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb
index 2374318eb..42698f7c7 100644
--- a/app/controllers/profile_controller.rb
+++ b/app/controllers/profile_controller.rb
@@ -1,4 +1,7 @@
 class ProfileController < ApplicationController
   def show
   end
+
+  def entry
+  end
 end
diff --git a/app/helpers/atom_helper.rb b/app/helpers/atom_helper.rb
index 7c8f5ed1a..f091d1afe 100644
--- a/app/helpers/atom_helper.rb
+++ b/app/helpers/atom_helper.rb
@@ -93,6 +93,87 @@ module AtomHelper
     xml['poco'].note account.note
   end
 
+  def in_reply_to(xml, uri, url)
+    xml['thr'].send('in-reply-to', { ref: uri, href: url, type: 'text/html' })
+  end
+
+  def disambiguate_uri(target)
+    if target.local?
+      if target.object_type == :person
+        profile_url(name: target.username)
+      else
+        unique_tag(target.stream_entry.created_at, target.stream_entry.activity_id, target.stream_entry.activity_type)
+      end
+    else
+      target.uri
+    end
+  end
+
+  def disambiguate_url(target)
+    if target.local?
+      if target.object_type == :person
+        profile_url(name: target.username)
+      else
+        status_url(name: target.stream_entry.account.username, id: target.stream_entry.id)
+      end
+    else
+      target.url
+    end
+  end
+
+  def link_mention(xml, account)
+    xml.link(rel: 'mentioned', href: disambiguate_uri(account))
+  end
+
+  def include_author(xml, account)
+    object_type      xml, :person
+    uri              xml, profile_url(name: account.username)
+    name             xml, account.username
+    summary          xml, account.note
+    link_alternate   xml, profile_url(name: account.username)
+    portable_contact xml, account
+  end
+
+  def include_entry(xml, stream_entry)
+    unique_id    xml, stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type
+    published_at xml, stream_entry.activity.created_at
+    updated_at   xml, stream_entry.activity.updated_at
+    title        xml, stream_entry.title
+    content      xml, stream_entry.content
+    verb         xml, stream_entry.verb
+    link_self    xml, atom_entry_url(id: stream_entry.id)
+    object_type  xml, stream_entry.object_type
+
+    # Comments need thread element
+    if stream_entry.threaded?
+      in_reply_to xml, disambiguate_uri(stream_entry.thread), disambiguate_url(stream_entry.thread)
+    end
+
+    if stream_entry.targeted?
+      target(xml) do
+        object_type    xml, stream_entry.target.object_type
+        simple_id      xml, disambiguate_uri(stream_entry.target)
+        title          xml, stream_entry.target.title
+        link_alternate xml, disambiguate_url(stream_entry.target)
+
+        # People have summary and portable contacts information
+        if stream_entry.target.object_type == :person
+          summary          xml, stream_entry.target.content
+          portable_contact xml, stream_entry.target
+        end
+
+        # Statuses have content
+        if [:note, :comment].include? stream_entry.target.object_type
+          content xml, stream_entry.target.content
+        end
+      end
+    end
+
+    stream_entry.mentions.each do |mentioned|
+      link_mention xml, mentioned
+    end
+  end
+
   private
 
   def root_tag(xml, tag, &block)
diff --git a/app/models/account.rb b/app/models/account.rb
index 42d92eddf..fc399d69c 100644
--- a/app/models/account.rb
+++ b/app/models/account.rb
@@ -5,6 +5,7 @@ class Account < ActiveRecord::Base
   # Timelines
   has_many :stream_entries, inverse_of: :account
   has_many :statuses, inverse_of: :account
+  has_many :favourites, inverse_of: :account
 
   # Follow relations
   has_many :active_relationships,  class_name: 'Follow', foreign_key: 'account_id',        dependent: :destroy
@@ -41,7 +42,7 @@ class Account < ActiveRecord::Base
     self.username
   end
 
-  def summary
+  def content
     self.note
   end
 
diff --git a/app/models/favourite.rb b/app/models/favourite.rb
new file mode 100644
index 000000000..20260f46b
--- /dev/null
+++ b/app/models/favourite.rb
@@ -0,0 +1,38 @@
+class Favourite < ActiveRecord::Base
+  belongs_to :account, inverse_of: :favourites
+  belongs_to :status,  inverse_of: :favourites
+
+  has_one :stream_entry, as: :activity
+
+  def verb
+    :favorite
+  end
+
+  def title
+    "#{self.account.acct} favourited a status by #{self.status.account.acct}"
+  end
+
+  def content
+    title
+  end
+
+  def object_type
+    target.object_type
+  end
+
+  def target
+    self.status
+  end
+
+  def mentions
+    []
+  end
+
+  def thread
+    target
+  end
+
+  after_create do
+    self.account.stream_entries.create!(activity: self)
+  end
+end
diff --git a/app/models/follow.rb b/app/models/follow.rb
index 203215947..aa723d705 100644
--- a/app/models/follow.rb
+++ b/app/models/follow.rb
@@ -2,20 +2,23 @@ class Follow < ActiveRecord::Base
   belongs_to :account
   belongs_to :target_account, class_name: 'Account'
 
+  has_one :stream_entry, as: :activity
+
   validates :account, :target_account, presence: true
+  validates :account_id, uniqueness: { scope: :target_account_id }
 
   def verb
     :follow
   end
 
-  def object_type
-    :person
-  end
-
   def target
     self.target_account
   end
 
+  def object_type
+    target.object_type
+  end
+
   def content
     "#{self.account.acct} started following #{self.target_account.acct}"
   end
@@ -24,6 +27,10 @@ class Follow < ActiveRecord::Base
     content
   end
 
+  def mentions
+    []
+  end
+
   after_create do
     self.account.stream_entries.create!(activity: self)
   end
diff --git a/app/models/status.rb b/app/models/status.rb
index c0b0ca9d9..72bf1b790 100644
--- a/app/models/status.rb
+++ b/app/models/status.rb
@@ -1,24 +1,56 @@
 class Status < ActiveRecord::Base
   belongs_to :account, inverse_of: :statuses
 
+  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
+
   validates :account, presence: true
+  validates :uri, uniqueness: true, unless: 'local?'
+
+  def local?
+    self.uri.nil?
+  end
+
+  def reblog?
+    !self.reblog_of_id.nil?
+  end
+
+  def reply?
+    !self.in_reply_to_id.nil?
+  end
 
   def verb
-    :post
+    reblog? ? :share : :post
   end
 
   def object_type
-    :note
+    reply? ? :comment : :note
   end
 
   def content
-    self.text
+    reblog? ? self.reblog.text : self.text
+  end
+
+  def target
+    self.reblog
   end
 
   def title
     content.truncate(80, omission: "...")
   end
 
+  def mentions
+    m = []
+
+    m << thread.account if reply?
+    m << reblog.account if reblog?
+
+    m
+  end
+
   after_create do
     self.account.stream_entries.create!(activity: self)
   end
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
index 7a182bb5d..a3ae099a1 100644
--- a/app/models/stream_entry.rb
+++ b/app/models/stream_entry.rb
@@ -5,7 +5,7 @@ class StreamEntry < ActiveRecord::Base
   validates :account, :activity, presence: true
 
   def object_type
-    self.activity.object_type
+    targeted? ? :activity : self.activity.object_type
   end
 
   def verb
@@ -13,7 +13,7 @@ class StreamEntry < ActiveRecord::Base
   end
 
   def targeted?
-    [:follow].include? self.verb
+    [:follow, :share, :favorite].include? verb
   end
 
   def target
@@ -27,4 +27,16 @@ class StreamEntry < ActiveRecord::Base
   def content
     self.activity.content
   end
+
+  def threaded?
+    [:favorite, :comment].include? verb
+  end
+
+  def thread
+    self.activity.thread
+  end
+
+  def mentions
+    self.activity.mentions
+  end
 end
diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb
index bd3c760d7..405d6c62e 100644
--- a/app/services/follow_remote_account_service.rb
+++ b/app/services/follow_remote_account_service.rb
@@ -15,6 +15,7 @@ class FollowRemoteAccountService
 
     account.remote_url  = data.link('http://schemas.google.com/g/2010#updates-from').href
     account.salmon_url  = data.link('salmon').href
+    account.url         = data.link('http://webfinger.net/rel/profile-page').href
     account.public_key  = magic_key_to_pem(data.link('magic-public-key').href)
     account.private_key = nil
 
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
index 6f9b7cf73..dd9e76956 100644
--- a/app/services/process_interaction_service.rb
+++ b/app/services/process_interaction_service.rb
@@ -3,10 +3,10 @@ class ProcessInteractionService
     body = salmon.unpack(envelope)
     xml  = Nokogiri::XML(body)
 
-    return if !involves_target_account(xml, target_account) || xml.at_xpath('//author/name').nil? || xml.at_xpath('//author/uri').nil?
+    return if !involves_target_account(xml, target_account) || xml.at_xpath('//xmlns:author/xmlns:name').nil? || xml.at_xpath('//xmlns:author/xmlns:uri').nil?
 
-    username = xml.at_xpath('//author/name').content
-    url      = xml.at_xpath('//author/uri').content
+    username = xml.at_xpath('//xmlns:author/xmlns:name').content
+    url      = xml.at_xpath('//xmlns:author/xmlns:uri').content
     domain   = Addressable::URI.parse(url).host
     account  = Account.find_by(username: username, domain: domain)
 
diff --git a/app/views/atom/entry.xml.ruby b/app/views/atom/entry.xml.ruby
index 2a26e624a..e0e089f46 100644
--- a/app/views/atom/entry.xml.ruby
+++ b/app/views/atom/entry.xml.ruby
@@ -1,37 +1,9 @@
 Nokogiri::XML::Builder.new do |xml|
   entry(xml, true) do
-    unique_id    xml, @entry.created_at, @entry.activity_id, @entry.activity_type
-    published_at xml, @entry.activity.created_at
-    updated_at   xml, @entry.activity.updated_at
-    title        xml, @entry.title
-    content      xml, @entry.content
-    verb         xml, @entry.verb
-
     author(xml) do
-      object_type      xml, :person
-      uri              xml, profile_url(name: @entry.account.username)
-      name             xml, @entry.account.username
-      summary          xml, @entry.account.note
-      link_alternate   xml, profile_url(name: @entry.account.username)
-      portable_contact xml, @entry.account
-    end
-
-    if @entry.targeted?
-      target(xml) do
-        object_type    xml, @entry.target.object_type
-        simple_id      xml, @entry.target.uri
-        title          xml, @entry.target.title
-        summary        xml, @entry.target.summary
-        link_alternate xml, @entry.target.uri
-
-        if @entry.target.object_type == :person
-          portable_contact xml, @entry.target
-        end
-      end
-    else
-      object_type xml, @entry.object_type
+      include_author xml, @entry.account
     end
 
-    link_self xml, atom_entry_url(id: @entry.id)
+    include_entry xml, @entry
   end
-end
+end.to_xml
diff --git a/app/views/atom/user_stream.xml.ruby b/app/views/atom/user_stream.xml.ruby
index 2b0c0aaa0..d7e0d5843 100644
--- a/app/views/atom/user_stream.xml.ruby
+++ b/app/views/atom/user_stream.xml.ruby
@@ -6,12 +6,7 @@ Nokogiri::XML::Builder.new do |xml|
     updated_at xml, stream_updated_at
 
     author(xml) do
-      object_type      xml, :person
-      uri              xml, profile_url(name: @account.username)
-      name             xml, @account.username
-      summary          xml, @account.note
-      link_alternate   xml, profile_url(name: @account.username)
-      portable_contact xml, @account
+      include_author xml, @account
     end
 
     link_alternate xml, profile_url(name: @account.username)
@@ -21,29 +16,7 @@ Nokogiri::XML::Builder.new do |xml|
 
     @account.stream_entries.order('id desc').each do |stream_entry|
       entry(xml, false) do
-        unique_id    xml, stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type
-        published_at xml, stream_entry.activity.created_at
-        updated_at   xml, stream_entry.activity.updated_at
-        title        xml, stream_entry.title
-        content      xml, stream_entry.content
-        verb         xml, stream_entry.verb
-        link_self    xml, atom_entry_url(id: stream_entry.id)
-
-        if stream_entry.targeted?
-          target(xml) do
-            object_type    xml, stream_entry.target.object_type
-            simple_id      xml, stream_entry.target.uri
-            title          xml, stream_entry.target.title
-            summary        xml, stream_entry.target.summary
-            link_alternate xml, stream_entry.target.uri
-
-            if stream_entry.target.object_type == :person
-              portable_contact xml, stream_entry.target
-            end
-          end
-        else
-          object_type xml, stream_entry.object_type
-        end
+        include_entry xml, stream_entry
       end
     end
   end
diff --git a/config/routes.rb b/config/routes.rb
index c57baea28..72cfd3e3e 100644
--- a/config/routes.rb
+++ b/config/routes.rb
@@ -5,6 +5,7 @@ Rails.application.routes.draw do
   get 'atom/entries/:id', to: 'atom#entry',       as: :atom_entry
   get 'atom/users/:id',   to: 'atom#user_stream', as: :atom_user_stream
   get 'users/:name',      to: 'profile#show',     as: :profile
+  get 'users/:name/:id',  to: 'profile#entry',    as: :status
 
   mount Mastodon::API => '/api/'
 
diff --git a/db/migrate/20160223162837_add_metadata_to_statuses.rb b/db/migrate/20160223162837_add_metadata_to_statuses.rb
new file mode 100644
index 000000000..7120e582a
--- /dev/null
+++ b/db/migrate/20160223162837_add_metadata_to_statuses.rb
@@ -0,0 +1,6 @@
+class AddMetadataToStatuses < ActiveRecord::Migration
+  def change
+    add_column :statuses, :in_reply_to_id, :integer, null: true
+    add_column :statuses, :reblog_of_id, :integer, null: true
+  end
+end
diff --git a/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb b/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb
new file mode 100644
index 000000000..0fc1c39c3
--- /dev/null
+++ b/db/migrate/20160223164502_make_uris_nullable_in_statuses.rb
@@ -0,0 +1,5 @@
+class MakeUrisNullableInStatuses < ActiveRecord::Migration
+  def change
+    change_column :statuses, :uri, :string, null: true, default: nil
+  end
+end
diff --git a/db/migrate/20160223165723_add_url_to_statuses.rb b/db/migrate/20160223165723_add_url_to_statuses.rb
new file mode 100644
index 000000000..a5aa1613a
--- /dev/null
+++ b/db/migrate/20160223165723_add_url_to_statuses.rb
@@ -0,0 +1,5 @@
+class AddUrlToStatuses < ActiveRecord::Migration
+  def change
+    add_column :statuses, :url, :string, null: true, default: nil
+  end
+end
diff --git a/db/migrate/20160223165855_add_url_to_accounts.rb b/db/migrate/20160223165855_add_url_to_accounts.rb
new file mode 100644
index 000000000..59dd2b97b
--- /dev/null
+++ b/db/migrate/20160223165855_add_url_to_accounts.rb
@@ -0,0 +1,5 @@
+class AddUrlToAccounts < ActiveRecord::Migration
+  def change
+    add_column :accounts, :url, :string, null: true, default: nil
+  end
+end
diff --git a/db/migrate/20160223171800_create_favourites.rb b/db/migrate/20160223171800_create_favourites.rb
new file mode 100644
index 000000000..bb35f491f
--- /dev/null
+++ b/db/migrate/20160223171800_create_favourites.rb
@@ -0,0 +1,12 @@
+class CreateFavourites < ActiveRecord::Migration
+  def change
+    create_table :favourites do |t|
+      t.integer :account_id, null: false
+      t.integer :status_id, null: false
+
+      t.timestamps null: false
+    end
+
+    add_index :favourites, [:account_id, :status_id], unique: true
+  end
+end
diff --git a/db/schema.rb b/db/schema.rb
index 7cd7c371d..28b382906 100644
--- a/db/schema.rb
+++ b/db/schema.rb
@@ -11,7 +11,7 @@
 #
 # It's strongly recommended that you check this file into your version control system.
 
-ActiveRecord::Schema.define(version: 20160222143943) do
+ActiveRecord::Schema.define(version: 20160223171800) do
 
   # These are extensions that must be enabled in order to support this database
   enable_extension "plpgsql"
@@ -31,10 +31,20 @@ ActiveRecord::Schema.define(version: 20160222143943) do
     t.text     "note",         default: "", null: false
     t.string   "display_name", default: "", null: false
     t.string   "uri",          default: "", null: false
+    t.string   "url"
   end
 
   add_index "accounts", ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
 
+  create_table "favourites", force: :cascade do |t|
+    t.integer  "account_id", null: false
+    t.integer  "status_id",  null: false
+    t.datetime "created_at", null: false
+    t.datetime "updated_at", null: false
+  end
+
+  add_index "favourites", ["account_id", "status_id"], name: "index_favourites_on_account_id_and_status_id", unique: true, using: :btree
+
   create_table "follows", force: :cascade do |t|
     t.integer  "account_id",        null: false
     t.integer  "target_account_id", null: false
@@ -45,11 +55,14 @@ ActiveRecord::Schema.define(version: 20160222143943) do
   add_index "follows", ["account_id", "target_account_id"], name: "index_follows_on_account_id_and_target_account_id", unique: true, using: :btree
 
   create_table "statuses", force: :cascade do |t|
-    t.string   "uri",        default: "", null: false
-    t.integer  "account_id",              null: false
-    t.text     "text",       default: "", null: false
-    t.datetime "created_at",              null: false
-    t.datetime "updated_at",              null: false
+    t.string   "uri"
+    t.integer  "account_id",                  null: false
+    t.text     "text",           default: "", null: false
+    t.datetime "created_at",                  null: false
+    t.datetime "updated_at",                  null: false
+    t.integer  "in_reply_to_id"
+    t.integer  "reblog_of_id"
+    t.string   "url"
   end
 
   add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree
diff --git a/spec/models/favourite_spec.rb b/spec/models/favourite_spec.rb
new file mode 100644
index 000000000..271aef4a4
--- /dev/null
+++ b/spec/models/favourite_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Favourite, type: :model do
+  pending "add some examples to (or delete) #{__FILE__}"
+end