commit: 709c6685a90bb819696566cc9e42e587546d72dc
parent: 9c4856bdb11fc9311ab30a97224cee3dfaec492f
Author: Eugen Rochko <eugen@zeonfederated.com>
Date: Mon, 22 Feb 2016 16:00:20 +0100
Made some progress
Diffstat:
63 files changed, 638 insertions(+), 105 deletions(-)
diff --git a/Gemfile b/Gemfile
@@ -38,6 +38,8 @@ group :development do
gem 'web-console', '~> 2.0'
gem 'spring'
gem 'rubocop', require: false
+ gem 'better_errors'
+ gem 'binding_of_caller'
end
group :production do
diff --git a/Gemfile.lock b/Gemfile.lock
@@ -43,6 +43,10 @@ GEM
descendants_tracker (~> 0.0.4)
ice_nine (~> 0.11.0)
thread_safe (~> 0.3, >= 0.3.1)
+ better_errors (2.1.1)
+ coderay (>= 1.0.0)
+ erubis (>= 2.6.6)
+ rack (>= 0.9.0)
binding_of_caller (0.7.2)
debug_inspector (>= 0.0.1)
builder (3.2.2)
@@ -284,6 +288,8 @@ PLATFORMS
DEPENDENCIES
addressable
+ better_errors
+ binding_of_caller
byebug
coffee-rails (~> 4.1.0)
dotenv-rails
diff --git a/app/api/mastodon/entities.rb b/app/api/mastodon/entities.rb
@@ -3,6 +3,8 @@ module Mastodon
class Account < Grape::Entity
expose :username
expose :domain
+ expose :display_name
+ expose :note
end
class Status < Grape::Entity
diff --git a/app/api/mastodon/ostatus.rb b/app/api/mastodon/ostatus.rb
@@ -8,12 +8,10 @@ module Mastodon
resource :subscriptions do
helpers do
- def subscription_url(account)
- "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}"
- end
+ include ApplicationHelper
end
- desc 'Receive updates from a feed'
+ desc 'Receive updates from an account'
params do
requires :id, type: String, desc: 'Account ID'
@@ -23,14 +21,14 @@ module Mastodon
body = request.body.read
if @account.subscription(subscription_url(@account)).verify(body, env['HTTP_X_HUB_SIGNATURE'])
- ProcessFeedUpdateService.new.(body, @account)
+ ProcessFeedService.new.(body, @account)
status 201
else
status 202
end
end
- desc 'Confirm PuSH subscription to a feed'
+ desc 'Confirm PuSH subscription to an account'
params do
requires :id, type: String, desc: 'Account ID'
@@ -49,14 +47,15 @@ module Mastodon
end
resource :salmon do
- desc 'Receive Salmon updates'
+ desc 'Receive Salmon updates targeted to account'
params do
requires :id, type: String, desc: 'Account ID'
end
post ':id' do
- # todo
+ ProcessInteractionService.new.(request.body.read, @account)
+ status 201
end
end
end
diff --git a/app/api/mastodon/rest.rb b/app/api/mastodon/rest.rb
@@ -5,9 +5,34 @@ module Mastodon
resource :statuses do
desc 'Return a public timeline'
+
get :all do
present Status.all, with: Mastodon::Entities::Status
end
+
+ desc 'Return the home timeline of a logged in user'
+
+ get :home do
+ # todo
+ end
+
+ desc 'Return the notifications timeline of a logged in user'
+
+ get :notifications do
+ # todo
+ end
+ end
+
+ resource :accounts do
+ desc 'Return a user profile'
+
+ params do
+ requires :id, type: String, desc: 'Account ID'
+ end
+
+ get ':id' do
+ present Account.find(params[:id]), with: Mastodon::Entities::Account
+ end
end
end
end
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
@@ -12,5 +12,4 @@
//
//= require jquery
//= require jquery_ujs
-//= require turbolinks
//= require_tree .
diff --git a/app/assets/javascripts/atom.coffee b/app/assets/javascripts/atom.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/home.coffee b/app/assets/javascripts/home.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/profile.coffee b/app/assets/javascripts/profile.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/javascripts/xrd.coffee b/app/assets/javascripts/xrd.coffee
@@ -0,0 +1,3 @@
+# Place all the behaviors and hooks related to the matching controller here.
+# All this logic will automatically be available in application.js.
+# You can use CoffeeScript in this file: http://coffeescript.org/
diff --git a/app/assets/stylesheets/atom.scss b/app/assets/stylesheets/atom.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Atom controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/home.scss b/app/assets/stylesheets/home.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Home controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/profile.scss b/app/assets/stylesheets/profile.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the Profile controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/assets/stylesheets/xrd.scss b/app/assets/stylesheets/xrd.scss
@@ -0,0 +1,3 @@
+// Place all the styles related to the XRD controller here.
+// They will automatically be included in application.css.
+// You can use Sass (SCSS) here: http://sass-lang.com/
diff --git a/app/controllers/atom_controller.rb b/app/controllers/atom_controller.rb
@@ -0,0 +1,14 @@
+class AtomController < ApplicationController
+ before_filter :set_format
+
+ def user_stream
+ @account = Account.find_by!(id: params[:id], domain: nil)
+ end
+
+ private
+
+ def set_format
+ request.format = 'xml'
+ response.headers['Content-Type'] = 'application/atom+xml'
+ end
+end
diff --git a/app/controllers/home_controller.rb b/app/controllers/home_controller.rb
@@ -0,0 +1,4 @@
+class HomeController < ApplicationController
+ def index
+ end
+end
diff --git a/app/controllers/profile_controller.rb b/app/controllers/profile_controller.rb
@@ -0,0 +1,4 @@
+class ProfileController < ApplicationController
+ def show
+ end
+end
diff --git a/app/controllers/xrd_controller.rb b/app/controllers/xrd_controller.rb
@@ -0,0 +1,39 @@
+class XrdController < ApplicationController
+ before_filter :set_format
+
+ def host_meta
+ @webfinger_template = "#{webfinger_url}?resource={uri}"
+ end
+
+ def webfinger
+ @account = Account.find_by!(username: username_from_resource, domain: nil)
+ @canonical_account_uri = "acct:#{@account.username}#{LOCAL_DOMAIN}"
+ @magic_key = pem_to_magic_key(@account.keypair.public_key)
+ end
+
+ private
+
+ def set_format
+ request.format = 'xml'
+ response.headers['Content-Type'] = 'application/xrd+xml'
+ end
+
+ def username_from_resource
+ params[:resource].split('@').first.gsub('acct:', '')
+ end
+
+ def pem_to_magic_key(public_key)
+ modulus, exponent = [public_key.n, public_key.e].map do |component|
+ result = ""
+
+ until component == 0 do
+ result << [component % 256].pack('C')
+ component >>= 8
+ end
+
+ result.reverse!
+ end
+
+ (["RSA"] + [modulus, exponent].map { |n| Base64.urlsafe_encode64(n) }).join('.')
+ end
+end
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
@@ -1,2 +1,19 @@
module ApplicationHelper
+ include GrapeRouteHelpers::NamedRouteMatcher
+
+ def unique_tag(date, id, type)
+ "tag:#{LOCAL_DOMAIN},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}"
+ end
+
+ def subscription_url(account)
+ add_base_url_prefix subscription_path(id: account.id, format: '')
+ end
+
+ def salmon_url(account)
+ add_base_url_prefix salmon_path(id: account.id, format: '')
+ end
+
+ def add_base_url_prefix(suffix)
+ "#{root_url}api#{suffix}"
+ end
end
diff --git a/app/helpers/atom_helper.rb b/app/helpers/atom_helper.rb
@@ -0,0 +1,5 @@
+module AtomHelper
+ def stream_updated_at
+ @account.stream_entries.last ? @account.stream_entries.last.created_at.iso8601 : @account.updated_at.iso8601
+ end
+end
diff --git a/app/helpers/home_helper.rb b/app/helpers/home_helper.rb
@@ -0,0 +1,2 @@
+module HomeHelper
+end
diff --git a/app/helpers/profile_helper.rb b/app/helpers/profile_helper.rb
@@ -0,0 +1,2 @@
+module ProfileHelper
+end
diff --git a/app/helpers/xrd_helper.rb b/app/helpers/xrd_helper.rb
@@ -0,0 +1,2 @@
+module XrdHelper
+end
diff --git a/app/models/account.rb b/app/models/account.rb
@@ -1,6 +1,38 @@
class Account < ActiveRecord::Base
+ # Local users
+ has_one :user, inverse_of: :account
+
+ # Timelines
+ has_many :stream_entries, inverse_of: :account
has_many :statuses, inverse_of: :account
+ # Follow relations
+ has_many :active_relationships, class_name: 'Follow', foreign_key: 'account_id', dependent: :destroy
+ has_many :passive_relationships, class_name: 'Follow', foreign_key: 'target_account_id', dependent: :destroy
+
+ has_many :following, through: :active_relationships, source: :target_account
+ has_many :followers, through: :passive_relationships, source: :account
+
+ def follow!(other_account)
+ self.active_relationships.create!(target_account: other_account)
+ end
+
+ def unfollow!(other_account)
+ self.active_relationships.find_by(target_account: other_account).destroy
+ end
+
+ def following?(other_account)
+ following.include?(other_account)
+ end
+
+ def local?
+ self.domain.nil?
+ end
+
+ def keypair
+ self.private_key.nil? ? OpenSSL::PKey::RSA.new(self.public_key) : OpenSSL::PKey::RSA.new(self.private_key)
+ end
+
def subscription(webhook_url)
@subscription ||= OStatus2::Subscription.new(self.remote_url, secret: self.secret, token: self.verify_token, webhook: webhook_url, hub: self.hub_url)
end
diff --git a/app/models/follow.rb b/app/models/follow.rb
@@ -0,0 +1,8 @@
+class Follow < ActiveRecord::Base
+ belongs_to :account
+ belongs_to :target_account, class_name: 'Account'
+
+ after_create do
+ self.account.stream_entries.create!(activity: self)
+ end
+end
diff --git a/app/models/status.rb b/app/models/status.rb
@@ -1,3 +1,7 @@
class Status < ActiveRecord::Base
belongs_to :account, inverse_of: :statuses
+
+ after_create do
+ self.account.stream_entries.create!(activity: self)
+ end
end
diff --git a/app/models/stream_entry.rb b/app/models/stream_entry.rb
@@ -0,0 +1,33 @@
+class StreamEntry < ActiveRecord::Base
+ belongs_to :account, inverse_of: :stream_entries
+ belongs_to :activity, polymorphic: true
+
+ def object_type
+ case self.activity_type
+ when 'Status'
+ :note
+ when 'Follow'
+ :person
+ end
+ end
+
+ def verb
+ case self.activity_type
+ when 'Status'
+ :post
+ when 'Follow'
+ :follow
+ end
+ end
+
+ def target
+ case self.activity_type
+ when 'Follow'
+ self.activity.target_account
+ end
+ end
+
+ def content
+ self.activity.text if self.activity_type == 'Status'
+ end
+end
diff --git a/app/models/user.rb b/app/models/user.rb
@@ -0,0 +1,3 @@
+class User < ActiveRecord::Base
+ belongs_to :account, inverse_of: :user
+end
diff --git a/app/services/fetch_feed_service.rb b/app/services/fetch_feed_service.rb
@@ -1,5 +1,15 @@
class FetchFeedService
def call(account)
- # todo
+ process_service.(http_client.get(account.remote_url), account)
+ end
+
+ private
+
+ def process_service
+ ProcessFeedService.new
+ end
+
+ def http_client
+ HTTP
end
end
diff --git a/app/services/follow_remote_account_service.rb b/app/services/follow_remote_account_service.rb
@@ -0,0 +1,57 @@
+class FollowRemoteAccountService
+ include ApplicationHelper
+
+ def call(uri)
+ username, domain = uri.split('@')
+ account = Account.where(username: username, domain: domain).first
+
+ return account unless account.nil?
+
+ account = Account.new(username: username, domain: domain)
+ data = Goldfinger.finger("acct:#{uri}")
+
+ account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
+ account.salmon_url = data.link('salmon').href
+ account.public_key = magic_key_to_pem(data.link('magic-public-key').href)
+ account.private_key = nil
+
+ account.secret = SecureRandom.hex
+ account.verify_token = SecureRandom.hex
+
+ feed = get_feed(account.remote_url)
+ hubs = feed.xpath('//xmlns:link[@rel="hub"]')
+
+ return false if hubs.empty? || hubs.first.attribute('href').nil? || feed.at_xpath('/xmlns:author/xmlns:uri').nil?
+
+ account.uri = feed.at_xpath('/xmlns:author/xmlns:uri').content
+ account.hub_url = hubs.first.attribute('href').value
+ account.save!
+
+ subscription = account.subscription(subscription_url(account))
+ subscription.subscribe
+ rescue Goldfinger::Error, HTTP::Error => e
+ false
+ end
+
+ private
+
+ def get_feed(url)
+ response = http_client.get(Addressable::URI.parse(url))
+ Nokogiri::XML(response)
+ end
+
+ def magic_key_to_pem(magic_key)
+ _, modulus, exponent = magic_key.split('.')
+ modulus, exponent = [modulus, exponent].map { |n| Base64.urlsafe_decode64(n).bytes.inject(0) { |num, byte| (num << 8) | byte } }
+
+ key = OpenSSL::PKey::RSA.new
+ key.n = modulus
+ key.e = exponent
+
+ key.to_pem
+ end
+
+ def http_client
+ HTTP
+ end
+end
diff --git a/app/services/follow_remote_user_service.rb b/app/services/follow_remote_user_service.rb
@@ -1,60 +0,0 @@
-class FollowRemoteUserService
- include GrapeRouteHelpers::NamedRouteMatcher
-
- def call(user)
- username, domain = user.split('@')
- account = Account.where(username: username, domain: domain).first
-
- return account unless account.nil?
-
- account = Account.new(username: username, domain: domain)
- data = Goldfinger.finger("acct:#{user}")
-
- account.remote_url = data.link('http://schemas.google.com/g/2010#updates-from').href
- account.salmon_url = data.link('salmon').href
- account.public_key = magic_key_to_pem(data.link('magic-public-key').href)
- account.private_key = nil
-
- account.secret = SecureRandom.hex
- account.verify_token = SecureRandom.hex
-
- feed = get_feed(account.remote_url)
- hubs = feed.xpath('//xmlns:link[@rel="hub"]')
-
- return false if hubs.empty? || hubs.first.attribute('href').nil?
-
- account.hub_url = hubs.first.attribute('href').value
- account.save!
-
- subscription = account.subscription(subscription_url(account))
- subscription.subscribe
- rescue Goldfinger::Error, HTTP::Error => e
- false
- end
-
- private
-
- def get_feed(url)
- response = http_client.get(Addressable::URI.parse(url))
- Nokogiri::XML(response)
- end
-
- def magic_key_to_pem(magic_key)
- _, modulus, exponent = magic_key.split('.')
- modulus, exponent = [modulus, exponent].map { |n| Base64.urlsafe_decode64(n).bytes.inject(0) { |num, byte| (num << 8) | byte } }
-
- key = OpenSSL::PKey::RSA.new
- key.n = modulus
- key.d = exponent
-
- key.to_pem
- end
-
- def http_client
- HTTP
- end
-
- def subscription_url(account)
- "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}"
- end
-end
diff --git a/app/services/follow_service.rb b/app/services/follow_service.rb
@@ -0,0 +1,12 @@
+class FollowService
+ def call(source_account, uri)
+ target_account = follow_remote_account_service.(uri)
+ source_account.follow!(target_account)
+ end
+
+ private
+
+ def follow_remote_account_service
+ FollowRemoteAccountService.new
+ end
+end
diff --git a/app/services/process_feed_service.rb b/app/services/process_feed_service.rb
@@ -0,0 +1,20 @@
+class ProcessFeedService
+ def call(body, account)
+ xml = Nokogiri::XML(body)
+
+ xml.xpath('/xmlns:feed/xmlns:entry').each do |entry|
+ uri = entry.at_xpath('./xmlns:id').content
+ status = Status.find_by(uri: uri)
+
+ next unless status.nil?
+
+ status = Status.new
+ status.account = account
+ status.uri = uri
+ status.text = entry.at_xpath('./xmlns:content').content
+ status.created_at = entry.at_xpath('./xmlns:published').content
+ status.updated_at = entry.at_xpath('./xmlns:updated').content
+ status.save!
+ end
+ end
+end
diff --git a/app/services/process_feed_update_service.rb b/app/services/process_feed_update_service.rb
@@ -1,20 +0,0 @@
-class ProcessFeedUpdateService
- def call(body, account)
- xml = Nokogiri::XML(body)
-
- xml.xpath('/xmlns:feed/xmlns:entry').each do |entry|
- uri = entry.at_xpath('./xmlns:id').content
- status = Status.find_by(uri: uri)
-
- next unless status.nil?
-
- status = Status.new
- status.account = account
- status.uri = uri
- status.text = entry.at_xpath('./xmlns:content').content
- status.created_at = entry.at_xpath('./xmlns:published').content
- status.updated_at = entry.at_xpath('./xmlns:updated').content
- status.save!
- end
- end
-end
diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb
@@ -0,0 +1,38 @@
+class ProcessInteractionService
+ def call(envelope, target_account)
+ body = salmon.unpack(envelope)
+ xml = Nokogiri::XML(body)
+
+ return if xml.at_xpath('//author/name').nil? || xml.at_xpath('//author/uri').nil?
+
+ username = xml.at_xpath('//author/name').content
+ url = xml.at_xpath('//author/uri').content
+ domain = Addressable::URI.parse(url).host
+ account = Account.find_by(username: username, domain: domain)
+
+ if account.nil?
+ account = follow_remote_account_service.("acct:#{username}@#{domain}")
+ end
+
+ if salmon.verify(envelope, account.keypair)
+ verb = xml.at_path('//activity:verb').content
+
+ case verb
+ when 'http://activitystrea.ms/schema/1.0/follow', 'follow'
+ account.follow!(target_account)
+ when 'http://activitystrea.ms/schema/1.0/unfollow', 'unfollow'
+ account.unfollow!(target_account)
+ end
+ end
+ end
+
+ private
+
+ def salmon
+ OStatus2::Salmon.new
+ end
+
+ def follow_remote_account_service
+ FollowRemoteAccountService.new
+ end
+end
diff --git a/app/services/setup_local_account_service.rb b/app/services/setup_local_account_service.rb
@@ -0,0 +1,14 @@
+class SetupLocalAccountService
+ def call(user, username)
+ user.build_account
+
+ user.account.username = username
+ user.account.domain = nil
+
+ keypair = OpenSSL::PKey::RSA.new(2048)
+ user.account.private_key = keypair.to_pem
+ user.account.public_key = keypair.public_key.to_pem
+
+ user.save!
+ end
+end
diff --git a/app/views/atom/user_stream.xml.ruby b/app/views/atom/user_stream.xml.ruby
@@ -0,0 +1,35 @@
+Nokogiri::XML::Builder.new do |xml|
+ xml.feed(xmlns: 'http://www.w3.org/2005/Atom', 'xmlns:thr': 'http://purl.org/syndication/thread/1.0', 'xmlns:activity': 'http://activitystrea.ms/spec/1.0/') do
+ xml.id_ atom_user_stream_url(id: @account.id)
+ xml.title @account.display_name
+ xml.subtitle @account.note
+ xml.updated stream_updated_at
+
+ xml.author do
+ xml['activity'].send('object-type', 'http://activitystrea.ms/schema/1.0/person')
+ xml.uri profile_url(name: @account.username)
+ xml.name @account.username
+ xml.summary @account.note
+
+ xml.link(rel: 'alternate', type: 'text/html', href: profile_url(name: @account.username))
+ end
+
+ xml.link(rel: 'alternate', type: 'text/html', href: profile_url(name: @account.username))
+ xml.link(rel: 'hub', href: '')
+ xml.link(rel: 'salmon', href: salmon_url(@account))
+ xml.link(rel: 'self', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
+
+ @account.stream_entries.each do |stream_entry|
+ xml.entry do
+ xml.id_ unique_tag(stream_entry.created_at, stream_entry.activity_id, stream_entry.activity_type)
+ xml.published stream_entry.activity.created_at.iso8601
+ xml.updated stream_entry.activity.updated_at.iso8601
+ xml.content({ type: 'html' }, stream_entry.content)
+ xml.title
+
+ xml['activity'].send('verb', "http://activitystrea.ms/schema/1.0/#{stream_entry.verb}")
+ xml['activity'].send('object-type', "http://activitystrea.ms/schema/1.0/#{stream_entry.object_type}")
+ end
+ end
+ end
+end.to_xml
diff --git a/app/views/home/index.html.haml b/app/views/home/index.html.haml
@@ -0,0 +1 @@
+Mastodon
diff --git a/app/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
@@ -1,14 +0,0 @@
-<!DOCTYPE html>
-<html>
-<head>
- <title>Mastodon</title>
- <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %>
- <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %>
- <%= csrf_meta_tags %>
-</head>
-<body>
-
-<%= yield %>
-
-</body>
-</html>
diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml
@@ -0,0 +1,10 @@
+!!!
+%html
+ %head
+ %meta{:content => "text/html; charset=UTF-8", "http-equiv" => "Content-Type"}/
+ %title Mastodon
+ = stylesheet_link_tag 'application', media: 'all'
+ = javascript_include_tag 'application'
+ = csrf_meta_tags
+ %body
+ = yield
diff --git a/app/views/profile/show.html.haml b/app/views/profile/show.html.haml
@@ -0,0 +1,2 @@
+%h1 Profile#show
+%p Find me in app/views/profile/show.html.haml
diff --git a/app/views/xrd/host_meta.xml.ruby b/app/views/xrd/host_meta.xml.ruby
@@ -0,0 +1,5 @@
+Nokogiri::XML::Builder.new do |xml|
+ xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do
+ xml.Link(rel: 'lrdd', type: 'application/xrd+xml', template: @webfinger_template)
+ end
+end.to_xml
diff --git a/app/views/xrd/webfinger.xml.ruby b/app/views/xrd/webfinger.xml.ruby
@@ -0,0 +1,8 @@
+Nokogiri::XML::Builder.new do |xml|
+ xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do
+ xml.Subject @canonical_account_uri
+ xml.Link(rel: 'http://schemas.google.com/g/2010#updates-from', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id))
+ xml.Link(rel: 'salmon', href: salmon_url(@account))
+ xml.Link(rel: 'magic-public-key', href: @magic_key)
+ end
+end.to_xml
diff --git a/config/environments/development.rb b/config/environments/development.rb
@@ -38,4 +38,6 @@ Rails.application.configure do
# Raises error for missing translations
# config.action_view.raise_on_missing_translations = true
+
+ config.action_mailer.default_url_options = { host: ENV['NGROK_HOST'] }
end
diff --git a/config/initializers/ostatus.rb b/config/initializers/ostatus.rb
@@ -0,0 +1 @@
+LOCAL_DOMAIN = ENV['LOCAL_DOMAIN'] || 'localhost'
diff --git a/config/routes.rb b/config/routes.rb
@@ -1,3 +1,11 @@
Rails.application.routes.draw do
+ get '.well-known/host-meta', to: 'xrd#host_meta', as: :host_meta
+ get '.well-known/webfinger', to: 'xrd#webfinger', as: :webfinger
+
+ get 'atom/:id', to: 'atom#user_stream', as: :atom_user_stream
+ get 'user/:name', to: 'profile#show', as: :profile
+
mount Mastodon::API => '/api/'
+
+ root 'home#index'
end
diff --git a/db/migrate/20160221003140_create_users.rb b/db/migrate/20160221003140_create_users.rb
@@ -0,0 +1,12 @@
+class CreateUsers < ActiveRecord::Migration
+ def change
+ create_table :users do |t|
+ t.string :email, null: false, default: ''
+ t.integer :account_id, null: false
+
+ t.timestamps null: false
+ end
+
+ add_index :users, :email, unique: true
+ end
+end
diff --git a/db/migrate/20160221003621_create_follows.rb b/db/migrate/20160221003621_create_follows.rb
@@ -0,0 +1,12 @@
+class CreateFollows < ActiveRecord::Migration
+ def change
+ create_table :follows do |t|
+ t.integer :account_id, null: false
+ t.integer :target_account_id, null: false
+
+ t.timestamps null: false
+ end
+
+ add_index :follows, [:account_id, :target_account_id], unique: true
+ end
+end
diff --git a/db/migrate/20160222122600_create_stream_entries.rb b/db/migrate/20160222122600_create_stream_entries.rb
@@ -0,0 +1,11 @@
+class CreateStreamEntries < ActiveRecord::Migration
+ def change
+ create_table :stream_entries do |t|
+ t.integer :account_id
+ t.integer :activity_id
+ t.string :activity_type
+
+ t.timestamps null: false
+ end
+ end
+end
diff --git a/db/migrate/20160222143943_add_profile_fields_to_accounts.rb b/db/migrate/20160222143943_add_profile_fields_to_accounts.rb
@@ -0,0 +1,7 @@
+class AddProfileFieldsToAccounts < ActiveRecord::Migration
+ def change
+ add_column :accounts, :note, :text, null: false, default: ''
+ add_column :accounts, :display_name, :string, null: false, default: ''
+ add_column :accounts, :uri, :string, null: false, default: ''
+ end
+end
diff --git 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: 20160220211917) do
+ActiveRecord::Schema.define(version: 20160222143943) do
# These are extensions that must be enabled in order to support this database
enable_extension "plpgsql"
@@ -28,10 +28,22 @@ ActiveRecord::Schema.define(version: 20160220211917) do
t.string "hub_url", default: "", null: false
t.datetime "created_at", null: false
t.datetime "updated_at", null: false
+ t.text "note", default: "", null: false
+ t.string "display_name", default: "", null: false
+ t.string "uri", default: "", null: false
end
add_index "accounts", ["username", "domain"], name: "index_accounts_on_username_and_domain", unique: true, using: :btree
+ create_table "follows", force: :cascade do |t|
+ t.integer "account_id", null: false
+ t.integer "target_account_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ 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
@@ -42,4 +54,21 @@ ActiveRecord::Schema.define(version: 20160220211917) do
add_index "statuses", ["uri"], name: "index_statuses_on_uri", unique: true, using: :btree
+ create_table "stream_entries", force: :cascade do |t|
+ t.integer "account_id"
+ t.integer "activity_id"
+ t.string "activity_type"
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ create_table "users", force: :cascade do |t|
+ t.string "email", default: "", null: false
+ t.integer "account_id", null: false
+ t.datetime "created_at", null: false
+ t.datetime "updated_at", null: false
+ end
+
+ add_index "users", ["email"], name: "index_users_on_email", unique: true, using: :btree
+
end
diff --git a/spec/controllers/atom_controller_spec.rb b/spec/controllers/atom_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe AtomController, type: :controller do
+
+end
diff --git a/spec/controllers/home_controller_spec.rb b/spec/controllers/home_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe HomeController, type: :controller do
+
+end
diff --git a/spec/controllers/profile_controller_spec.rb b/spec/controllers/profile_controller_spec.rb
@@ -0,0 +1,12 @@
+require 'rails_helper'
+
+RSpec.describe ProfileController, type: :controller do
+
+ describe "GET #show" do
+ it "returns http success" do
+ get :show
+ expect(response).to have_http_status(:success)
+ end
+ end
+
+end
diff --git a/spec/controllers/xrd_controller_spec.rb b/spec/controllers/xrd_controller_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe XrdController, type: :controller do
+
+end
diff --git a/spec/helpers/atom_helper_spec.rb b/spec/helpers/atom_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the AtomHelper. For example:
+#
+# describe AtomHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe AtomHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/home_helper_spec.rb b/spec/helpers/home_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the HomeHelper. For example:
+#
+# describe HomeHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe HomeHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/profile_helper_spec.rb b/spec/helpers/profile_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the ProfileHelper. For example:
+#
+# describe ProfileHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe ProfileHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/helpers/xrd_helper_spec.rb b/spec/helpers/xrd_helper_spec.rb
@@ -0,0 +1,15 @@
+require 'rails_helper'
+
+# Specs in this file have access to a helper object that includes
+# the XrdHelper. For example:
+#
+# describe XrdHelper do
+# describe "string concat" do
+# it "concats two strings with spaces" do
+# expect(helper.concat_strings("this","that")).to eq("this that")
+# end
+# end
+# end
+RSpec.describe XrdHelper, type: :helper do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/follow_spec.rb b/spec/models/follow_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Follow, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/stream_spec.rb b/spec/models/stream_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe Stream, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/models/user_spec.rb b/spec/models/user_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe User, type: :model do
+ pending "add some examples to (or delete) #{__FILE__}"
+end
diff --git a/spec/views/profile/show.html.haml_spec.rb b/spec/views/profile/show.html.haml_spec.rb
@@ -0,0 +1,5 @@
+require 'rails_helper'
+
+RSpec.describe "profile/show.html.haml", type: :view do
+ pending "add some examples to (or delete) #{__FILE__}"
+end