From 709c6685a90bb819696566cc9e42e587546d72dc Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Mon, 22 Feb 2016 16:00:20 +0100 Subject: [PATCH] Made some progress --- Gemfile | 2 + Gemfile.lock | 6 +++ app/api/mastodon/entities.rb | 2 + app/api/mastodon/ostatus.rb | 15 ++++--- app/api/mastodon/rest.rb | 25 ++++++++++++ app/assets/javascripts/application.js | 1 - app/assets/javascripts/atom.coffee | 3 ++ app/assets/javascripts/home.coffee | 3 ++ app/assets/javascripts/profile.coffee | 3 ++ app/assets/javascripts/xrd.coffee | 3 ++ app/assets/stylesheets/atom.scss | 3 ++ app/assets/stylesheets/home.scss | 3 ++ app/assets/stylesheets/profile.scss | 3 ++ app/assets/stylesheets/xrd.scss | 3 ++ app/controllers/atom_controller.rb | 14 +++++++ app/controllers/home_controller.rb | 4 ++ app/controllers/profile_controller.rb | 4 ++ app/controllers/xrd_controller.rb | 39 +++++++++++++++++++ app/helpers/application_helper.rb | 17 ++++++++ app/helpers/atom_helper.rb | 5 +++ app/helpers/home_helper.rb | 2 + app/helpers/profile_helper.rb | 2 + app/helpers/xrd_helper.rb | 2 + app/models/account.rb | 32 +++++++++++++++ app/models/follow.rb | 8 ++++ app/models/status.rb | 4 ++ app/models/stream_entry.rb | 33 ++++++++++++++++ app/models/user.rb | 3 ++ app/services/fetch_feed_service.rb | 12 +++++- ...ce.rb => follow_remote_account_service.rb} | 19 ++++----- app/services/follow_service.rb | 12 ++++++ ...ate_service.rb => process_feed_service.rb} | 2 +- app/services/process_interaction_service.rb | 38 ++++++++++++++++++ app/services/setup_local_account_service.rb | 14 +++++++ app/views/atom/user_stream.xml.ruby | 35 +++++++++++++++++ app/views/home/index.html.haml | 1 + app/views/layouts/application.html.erb | 14 ------- app/views/layouts/application.html.haml | 10 +++++ app/views/profile/show.html.haml | 2 + app/views/xrd/host_meta.xml.ruby | 5 +++ app/views/xrd/webfinger.xml.ruby | 8 ++++ config/environments/development.rb | 2 + config/initializers/ostatus.rb | 1 + config/routes.rb | 8 ++++ db/migrate/20160221003140_create_users.rb | 12 ++++++ db/migrate/20160221003621_create_follows.rb | 12 ++++++ .../20160222122600_create_stream_entries.rb | 11 ++++++ ...22143943_add_profile_fields_to_accounts.rb | 7 ++++ db/schema.rb | 31 ++++++++++++++- spec/controllers/atom_controller_spec.rb | 5 +++ spec/controllers/home_controller_spec.rb | 5 +++ spec/controllers/profile_controller_spec.rb | 12 ++++++ spec/controllers/xrd_controller_spec.rb | 5 +++ spec/helpers/atom_helper_spec.rb | 15 +++++++ spec/helpers/home_helper_spec.rb | 15 +++++++ spec/helpers/profile_helper_spec.rb | 15 +++++++ spec/helpers/xrd_helper_spec.rb | 15 +++++++ spec/models/follow_spec.rb | 5 +++ spec/models/stream_spec.rb | 5 +++ spec/models/user_spec.rb | 5 +++ spec/views/profile/show.html.haml_spec.rb | 5 +++ 61 files changed, 570 insertions(+), 37 deletions(-) create mode 100644 app/assets/javascripts/atom.coffee create mode 100644 app/assets/javascripts/home.coffee create mode 100644 app/assets/javascripts/profile.coffee create mode 100644 app/assets/javascripts/xrd.coffee create mode 100644 app/assets/stylesheets/atom.scss create mode 100644 app/assets/stylesheets/home.scss create mode 100644 app/assets/stylesheets/profile.scss create mode 100644 app/assets/stylesheets/xrd.scss create mode 100644 app/controllers/atom_controller.rb create mode 100644 app/controllers/home_controller.rb create mode 100644 app/controllers/profile_controller.rb create mode 100644 app/controllers/xrd_controller.rb create mode 100644 app/helpers/atom_helper.rb create mode 100644 app/helpers/home_helper.rb create mode 100644 app/helpers/profile_helper.rb create mode 100644 app/helpers/xrd_helper.rb create mode 100644 app/models/follow.rb create mode 100644 app/models/stream_entry.rb create mode 100644 app/models/user.rb rename app/services/{follow_remote_user_service.rb => follow_remote_account_service.rb} (81%) create mode 100644 app/services/follow_service.rb rename app/services/{process_feed_update_service.rb => process_feed_service.rb} (94%) create mode 100644 app/services/process_interaction_service.rb create mode 100644 app/services/setup_local_account_service.rb create mode 100644 app/views/atom/user_stream.xml.ruby create mode 100644 app/views/home/index.html.haml delete mode 100644 app/views/layouts/application.html.erb create mode 100644 app/views/layouts/application.html.haml create mode 100644 app/views/profile/show.html.haml create mode 100644 app/views/xrd/host_meta.xml.ruby create mode 100644 app/views/xrd/webfinger.xml.ruby create mode 100644 config/initializers/ostatus.rb create mode 100644 db/migrate/20160221003140_create_users.rb create mode 100644 db/migrate/20160221003621_create_follows.rb create mode 100644 db/migrate/20160222122600_create_stream_entries.rb create mode 100644 db/migrate/20160222143943_add_profile_fields_to_accounts.rb create mode 100644 spec/controllers/atom_controller_spec.rb create mode 100644 spec/controllers/home_controller_spec.rb create mode 100644 spec/controllers/profile_controller_spec.rb create mode 100644 spec/controllers/xrd_controller_spec.rb create mode 100644 spec/helpers/atom_helper_spec.rb create mode 100644 spec/helpers/home_helper_spec.rb create mode 100644 spec/helpers/profile_helper_spec.rb create mode 100644 spec/helpers/xrd_helper_spec.rb create mode 100644 spec/models/follow_spec.rb create mode 100644 spec/models/stream_spec.rb create mode 100644 spec/models/user_spec.rb create mode 100644 spec/views/profile/show.html.haml_spec.rb diff --git a/Gemfile b/Gemfile index 59eb08814f..f0a77f7b64 100644 --- 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 index ff72f4ddf1..fad87d1914 100644 --- 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 index a3f40ec48a..2e56a67df4 100644 --- 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 index fcde980f71..4676bc429e 100644 --- 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 index e011ab34de..eaf337938e 100644 --- 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 index e07c5a830f..646c5aba4e 100644 --- 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 new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ 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 new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ 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 new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ 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 new file mode 100644 index 0000000000..24f83d18bb --- /dev/null +++ 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 new file mode 100644 index 0000000000..888698db31 --- /dev/null +++ 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 new file mode 100644 index 0000000000..7131aac4df --- /dev/null +++ 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 new file mode 100644 index 0000000000..22ee508768 --- /dev/null +++ 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 new file mode 100644 index 0000000000..62391c7d37 --- /dev/null +++ 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 new file mode 100644 index 0000000000..e0b45c5800 --- /dev/null +++ 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 new file mode 100644 index 0000000000..95f29929ca --- /dev/null +++ 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 new file mode 100644 index 0000000000..2374318ebb --- /dev/null +++ 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 new file mode 100644 index 0000000000..4c8e958e65 --- /dev/null +++ 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 index de6be7945c..29e444a327 100644 --- 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 new file mode 100644 index 0000000000..a42a499468 --- /dev/null +++ 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 new file mode 100644 index 0000000000..23de56ac60 --- /dev/null +++ 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 new file mode 100644 index 0000000000..5a0d6b31f8 --- /dev/null +++ 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 new file mode 100644 index 0000000000..6b273e1226 --- /dev/null +++ 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 index c0b153794d..90e8d7610b 100644 --- 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 new file mode 100644 index 0000000000..eec01b9ba2 --- /dev/null +++ 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 index a1278ccaa2..d98297643f 100644 --- 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 new file mode 100644 index 0000000000..cee151a077 --- /dev/null +++ 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 new file mode 100644 index 0000000000..ccfa54e4f4 --- /dev/null +++ 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 index 3b8efbe3b6..059d659255 100644 --- 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_user_service.rb b/app/services/follow_remote_account_service.rb similarity index 81% rename from app/services/follow_remote_user_service.rb rename to app/services/follow_remote_account_service.rb index f3c0e68df5..41f8fa4a0a 100644 --- a/app/services/follow_remote_user_service.rb +++ b/app/services/follow_remote_account_service.rb @@ -1,14 +1,14 @@ -class FollowRemoteUserService - include GrapeRouteHelpers::NamedRouteMatcher +class FollowRemoteAccountService + include ApplicationHelper - def call(user) - username, domain = user.split('@') + 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:#{user}") + 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 @@ -21,8 +21,9 @@ class FollowRemoteUserService feed = get_feed(account.remote_url) hubs = feed.xpath('//xmlns:link[@rel="hub"]') - return false if hubs.empty? || hubs.first.attribute('href').nil? + 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! @@ -45,7 +46,7 @@ class FollowRemoteUserService key = OpenSSL::PKey::RSA.new key.n = modulus - key.d = exponent + key.e = exponent key.to_pem end @@ -53,8 +54,4 @@ class FollowRemoteUserService 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 new file mode 100644 index 0000000000..fc606730b8 --- /dev/null +++ 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_update_service.rb b/app/services/process_feed_service.rb similarity index 94% rename from app/services/process_feed_update_service.rb rename to app/services/process_feed_service.rb index 0585fad7a5..f2523a3133 100644 --- a/app/services/process_feed_update_service.rb +++ b/app/services/process_feed_service.rb @@ -1,4 +1,4 @@ -class ProcessFeedUpdateService +class ProcessFeedService def call(body, account) xml = Nokogiri::XML(body) diff --git a/app/services/process_interaction_service.rb b/app/services/process_interaction_service.rb new file mode 100644 index 0000000000..8262ead8f5 --- /dev/null +++ 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 new file mode 100644 index 0000000000..c40e51855c --- /dev/null +++ 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 new file mode 100644 index 0000000000..d418ea0ecb --- /dev/null +++ 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 new file mode 100644 index 0000000000..862374a98f --- /dev/null +++ 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 deleted file mode 100644 index ff0d4c8653..0000000000 --- a/app/views/layouts/application.html.erb +++ /dev/null @@ -1,14 +0,0 @@ - - - - Mastodon - <%= stylesheet_link_tag 'application', media: 'all', 'data-turbolinks-track' => true %> - <%= javascript_include_tag 'application', 'data-turbolinks-track' => true %> - <%= csrf_meta_tags %> - - - -<%= yield %> - - - diff --git a/app/views/layouts/application.html.haml b/app/views/layouts/application.html.haml new file mode 100644 index 0000000000..0cb4e96e8f --- /dev/null +++ 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 new file mode 100644 index 0000000000..dcb5764ec0 --- /dev/null +++ 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 new file mode 100644 index 0000000000..07d0264711 --- /dev/null +++ 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 new file mode 100644 index 0000000000..7a1e9a1d34 --- /dev/null +++ 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 index b55e2144b6..c3377aac82 100644 --- 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 new file mode 100644 index 0000000000..64204870bb --- /dev/null +++ b/config/initializers/ostatus.rb @@ -0,0 +1 @@ +LOCAL_DOMAIN = ENV['LOCAL_DOMAIN'] || 'localhost' diff --git a/config/routes.rb b/config/routes.rb index fed31a3028..9ddc00c8f5 100644 --- 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 new file mode 100644 index 0000000000..c9750c6238 --- /dev/null +++ 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 new file mode 100644 index 0000000000..afec3dee03 --- /dev/null +++ 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 new file mode 100644 index 0000000000..10a6862d9f --- /dev/null +++ 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 new file mode 100644 index 0000000000..221142bdd4 --- /dev/null +++ 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 index 49ba23f19b..7cd7c371dd 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: 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 new file mode 100644 index 0000000000..ec14db007b --- /dev/null +++ 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 new file mode 100644 index 0000000000..e672b25e4d --- /dev/null +++ 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 new file mode 100644 index 0000000000..5904f5140e --- /dev/null +++ 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 new file mode 100644 index 0000000000..03a4b58009 --- /dev/null +++ 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 new file mode 100644 index 0000000000..57b12de673 --- /dev/null +++ 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 new file mode 100644 index 0000000000..e537d8d9a8 --- /dev/null +++ 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 new file mode 100644 index 0000000000..154c7dfb74 --- /dev/null +++ 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 new file mode 100644 index 0000000000..63ca2268a4 --- /dev/null +++ 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 new file mode 100644 index 0000000000..9b76332f63 --- /dev/null +++ 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 new file mode 100644 index 0000000000..7fc7756528 --- /dev/null +++ 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 new file mode 100644 index 0000000000..47a31bb435 --- /dev/null +++ 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 new file mode 100644 index 0000000000..778dcff323 --- /dev/null +++ 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