@@ -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 | |||
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 |
@@ -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 |
@@ -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 |