forked from treehouse/mastodon
Adding reblogs, favourites, improving atom generation
parent
3b0bc18db9
commit
fa33750105
|
@ -1,4 +1,7 @@
|
|||
class ProfileController < ApplicationController
|
||||
def show
|
||||
end
|
||||
|
||||
def entry
|
||||
end
|
||||
end
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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
|
||||
|
||||
|
|
|
@ -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)
|
||||
|
||||
|
|
|
@ -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
|
||||
include_author 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
|
||||
end
|
||||
|
||||
link_self xml, atom_entry_url(id: @entry.id)
|
||||
include_entry xml, @entry
|
||||
end
|
||||
end
|
||||
end.to_xml
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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/'
|
||||
|
||||
|
|
|
@ -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
|
|
@ -0,0 +1,5 @@
|
|||
class MakeUrisNullableInStatuses < ActiveRecord::Migration
|
||||
def change
|
||||
change_column :statuses, :uri, :string, null: true, default: nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddUrlToStatuses < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :statuses, :url, :string, null: true, default: nil
|
||||
end
|
||||
end
|
|
@ -0,0 +1,5 @@
|
|||
class AddUrlToAccounts < ActiveRecord::Migration
|
||||
def change
|
||||
add_column :accounts, :url, :string, null: true, default: nil
|
||||
end
|
||||
end
|
|
@ -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
|
25
db/schema.rb
25
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
|
||||
|
|
|
@ -0,0 +1,5 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Favourite, type: :model do
|
||||
pending "add some examples to (or delete) #{__FILE__}"
|
||||
end
|
Loading…
Reference in New Issue