Adding reblogs, favourites, improving atom generation

This commit is contained in:
Eugen Rochko 2016-02-23 19:17:37 +01:00
parent 3b0bc18db9
commit fa33750105
19 changed files with 251 additions and 79 deletions

View file

@ -1,4 +1,7 @@
class ProfileController < ApplicationController class ProfileController < ApplicationController
def show def show
end end
def entry
end
end end

View file

@ -93,6 +93,87 @@ module AtomHelper
xml['poco'].note account.note xml['poco'].note account.note
end 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 private
def root_tag(xml, tag, &block) def root_tag(xml, tag, &block)

View file

@ -5,6 +5,7 @@ class Account < ActiveRecord::Base
# Timelines # Timelines
has_many :stream_entries, inverse_of: :account has_many :stream_entries, inverse_of: :account
has_many :statuses, inverse_of: :account has_many :statuses, inverse_of: :account
has_many :favourites, inverse_of: :account
# Follow relations # Follow relations
has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
@ -41,7 +42,7 @@ class Account < ActiveRecord::Base
self.username self.username
end end
def summary def content
self.note self.note
end end

38
app/models/favourite.rb Normal file
View file

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

View file

@ -2,20 +2,23 @@ class Follow < ActiveRecord::Base
belongs_to :account belongs_to :account
belongs_to :target_account, class_name: 'Account' belongs_to :target_account, class_name: 'Account'
has_one :stream_entry, as: :activity
validates :account, :target_account, presence: true validates :account, :target_account, presence: true
validates :account_id, uniqueness: { scope: :target_account_id }
def verb def verb
:follow :follow
end end
def object_type
:person
end
def target def target
self.target_account self.target_account
end end
def object_type
target.object_type
end
def content def content
"#{self.account.acct} started following #{self.target_account.acct}" "#{self.account.acct} started following #{self.target_account.acct}"
end end
@ -24,6 +27,10 @@ class Follow < ActiveRecord::Base
content content
end end
def mentions
[]
end
after_create do after_create do
self.account.stream_entries.create!(activity: self) self.account.stream_entries.create!(activity: self)
end end

View file

@ -1,24 +1,56 @@
class Status < ActiveRecord::Base class Status < ActiveRecord::Base
belongs_to :account, inverse_of: :statuses 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 :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 def verb
:post reblog? ? :share : :post
end end
def object_type def object_type
:note reply? ? :comment : :note
end end
def content def content
self.text reblog? ? self.reblog.text : self.text
end
def target
self.reblog
end end
def title def title
content.truncate(80, omission: "...") content.truncate(80, omission: "...")
end end
def mentions
m = []
m << thread.account if reply?
m << reblog.account if reblog?
m
end
after_create do after_create do
self.account.stream_entries.create!(activity: self) self.account.stream_entries.create!(activity: self)
end end

View file

@ -5,7 +5,7 @@ class StreamEntry < ActiveRecord::Base
validates :account, :activity, presence: true validates :account, :activity, presence: true
def object_type def object_type
self.activity.object_type targeted? ? :activity : self.activity.object_type
end end
def verb def verb
@ -13,7 +13,7 @@ class StreamEntry < ActiveRecord::Base
end end
def targeted? def targeted?
[:follow].include? self.verb [:follow, :share, :favorite].include? verb
end end
def target def target
@ -27,4 +27,16 @@ class StreamEntry < ActiveRecord::Base
def content def content
self.activity.content self.activity.content
end end
def threaded?
[:favorite, :comment].include? verb
end
def thread
self.activity.thread
end
def mentions
self.activity.mentions
end
end end

View file

@ -15,6 +15,7 @@ class FollowRemoteAccountService
account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
account.salmon_url = data.link('salmon').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.public_key = magic_key_to_pem(data.link('magic-public-key').href)
account.private_key = nil account.private_key = nil

View file

@ -3,10 +3,10 @@ class ProcessInteractionService
body = salmon.unpack(envelope) body = salmon.unpack(envelope)
xml = Nokogiri::XML(body) 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 username = xml.at_xpath('//xmlns:author/xmlns:name').content
url = xml.at_xpath('//author/uri').content url = xml.at_xpath('//xmlns:author/xmlns:uri').content
domain = Addressable::URI.parse(url).host domain = Addressable::URI.parse(url).host
account = Account.find_by(username: username, domain: domain) account = Account.find_by(username: username, domain: domain)

View file

@ -1,37 +1,9 @@
Nokogiri::XML::Builder.new do |xml| Nokogiri::XML::Builder.new do |xml|
entry(xml, true) do 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 author(xml) do
object_type xml, :person include_author xml, @entry.account
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 end
if @entry.targeted? include_entry xml, @entry
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)
end end
end end.to_xml

View file

@ -6,12 +6,7 @@ Nokogiri::XML::Builder.new do |xml|
updated_at xml, stream_updated_at updated_at xml, stream_updated_at
author(xml) do author(xml) do
object_type xml, :person include_author xml, @account
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 end
link_alternate xml, profile_url(name: @account.username) 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| @account.stream_entries.order('id desc').each do |stream_entry|
entry(xml, false) do entry(xml, false) do
unique_id xml, stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type include_entry xml, stream_entry
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
end end
end end
end end

View file

@ -5,6 +5,7 @@ Rails.application.routes.draw do
get 'atom/entries/:id', to: 'atom#entry', as: :atom_entry get 'atom/entries/:id', to: 'atom#entry', as: :atom_entry
get 'atom/users/:id', to: 'atom#user_stream', as: :atom_user_stream get 'atom/users/:id', to: 'atom#user_stream', as: :atom_user_stream
get 'users/:name', to: 'profile#show', as: :profile get 'users/:name', to: 'profile#show', as: :profile
get 'users/:name/:id', to: 'profile#entry', as: :status
mount Mastodon::API => '/api/' mount Mastodon::API => '/api/'

View file

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

View file

@ -0,0 +1,5 @@
class MakeUrisNullableInStatuses < ActiveRecord::Migration
def change
change_column :statuses, :uri, :string, null: true, default: nil
end
end

View file

@ -0,0 +1,5 @@
class AddUrlToStatuses < ActiveRecord::Migration
def change
add_column :statuses, :url, :string, null: true, default: nil
end
end

View file

@ -0,0 +1,5 @@
class AddUrlToAccounts < ActiveRecord::Migration
def change
add_column :accounts, :url, :string, null: true, default: nil
end
end

View file

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

View file

@ -11,7 +11,7 @@
# #
# It's strongly recommended that you check this file into your version control system. # 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 # These are extensions that must be enabled in order to support this database
enable_extension "plpgsql" enable_extension "plpgsql"
@ -31,10 +31,20 @@ ActiveRecord::Schema.define(version: 20160222143943) do
t.text "note", default: "", null: false t.text "note", default: "", null: false
t.string "display_name", default: "", null: false t.string "display_name", default: "", null: false
t.string "uri", default: "", null: false t.string "uri", default: "", null: false
t.string "url"
end end
add_index "accounts", ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree 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| create_table "follows", force: :cascade do |t|
t.integer "account_id", null: false t.integer "account_id", null: false
t.integer "target_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 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| create_table "statuses", force: :cascade do |t|
t.string "uri", default: "", null: false t.string "uri"
t.integer "account_id", null: false t.integer "account_id", null: false
t.text "text", default: "", null: false t.text "text", default: "", null: false
t.datetime "created_at", null: false t.datetime "created_at", null: false
t.datetime "updated_at", null: false t.datetime "updated_at", null: false
t.integer "in_reply_to_id"
t.integer "reblog_of_id"
t.string "url"
end end
add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree

View file

@ -0,0 +1,5 @@
require 'rails_helper'
RSpec.describe Favourite, type: :model do
pending "add some examples to (or delete) #{__FILE__}"
end