about summary refs log tree commit diff
path: root/app
diff options
context:
space:
mode:
authorEugen Rochko <eugen@zeonfederated.com>2016-02-20 22:53:20 +0100
committerEugen Rochko <eugen@zeonfederated.com>2016-02-20 22:53:20 +0100
commit9c4856bdb11fc9311ab30a97224cee3dfaec492f (patch)
tree37fd831e505f040bbd3c583f56d3502ebd75e9c8 /app
Initial commit
Diffstat (limited to 'app')
-rw-r--r--app/api/mastodon/api.rb8
-rw-r--r--app/api/mastodon/entities.rb21
-rw-r--r--app/api/mastodon/ostatus.rb63
-rw-r--r--app/api/mastodon/rest.rb13
-rw-r--r--app/assets/images/.keep0
-rw-r--r--app/assets/javascripts/application.js16
-rw-r--r--app/assets/stylesheets/application.css15
-rw-r--r--app/controllers/application_controller.rb5
-rw-r--r--app/controllers/concerns/.keep0
-rw-r--r--app/helpers/application_helper.rb2
-rw-r--r--app/mailers/.keep0
-rw-r--r--app/models/.keep0
-rw-r--r--app/models/account.rb7
-rw-r--r--app/models/concerns/.keep0
-rw-r--r--app/models/status.rb3
-rw-r--r--app/services/fetch_feed_service.rb5
-rw-r--r--app/services/follow_remote_user_service.rb60
-rw-r--r--app/services/process_feed_update_service.rb20
-rw-r--r--app/views/layouts/application.html.erb14
19 files changed, 252 insertions, 0 deletions
diff --git a/app/api/mastodon/api.rb b/app/api/mastodon/api.rb
new file mode 100644
index 000000000..39469dc65
--- /dev/null
+++ b/app/api/mastodon/api.rb
@@ -0,0 +1,8 @@
+module Mastodon
+  class API < Grape::API
+    rescue_from :all
+
+    mount Mastodon::Ostatus
+    mount Mastodon::Rest
+  end
+end
diff --git a/app/api/mastodon/entities.rb b/app/api/mastodon/entities.rb
new file mode 100644
index 000000000..a3f40ec48
--- /dev/null
+++ b/app/api/mastodon/entities.rb
@@ -0,0 +1,21 @@
+module Mastodon
+  module Entities
+    class Account < Grape::Entity
+      expose :username
+      expose :domain
+    end
+
+    class Status < Grape::Entity
+      format_with(:iso_timestamp) { |dt| dt.iso8601 }
+
+      expose :uri
+      expose :text
+      expose :account, using: Mastodon::Entities::Account
+
+      with_options(format_with: :iso_timestamp) do
+        expose :created_at
+        expose :updated_at
+      end
+    end
+  end
+end
diff --git a/app/api/mastodon/ostatus.rb b/app/api/mastodon/ostatus.rb
new file mode 100644
index 000000000..fcde980f7
--- /dev/null
+++ b/app/api/mastodon/ostatus.rb
@@ -0,0 +1,63 @@
+module Mastodon
+  class Ostatus < Grape::API
+    format :txt
+
+    before do
+      @account = Account.find(params[:id])
+    end
+
+    resource :subscriptions do
+      helpers do
+        def subscription_url(account)
+          "https://649841dc.ngrok.io/api#{subscriptions_path(id: account.id)}"
+        end
+      end
+
+      desc 'Receive updates from a feed'
+
+      params do
+        requires :id, type: String, desc: 'Account ID'
+      end
+
+      post ':id' do
+        body = request.body.read
+
+        if @account.subscription(subscription_url(@account)).verify(body, env['HTTP_X_HUB_SIGNATURE'])
+          ProcessFeedUpdateService.new.(body, @account)
+          status 201
+        else
+          status 202
+        end
+      end
+
+      desc 'Confirm PuSH subscription to a feed'
+
+      params do
+        requires :id, type: String, desc: 'Account ID'
+        requires 'hub.topic', type: String, desc: 'Topic URL'
+        requires 'hub.verify_token', type: String, desc: 'Verification token'
+        requires 'hub.challenge', type: String, desc: 'Hub challenge'
+      end
+
+      get ':id' do
+        if @account.subscription(subscription_url(@account)).valid?(params['hub.topic'], params['hub.verify_token'])
+          params['hub.challenge']
+        else
+          error! :not_found, 404
+        end
+      end
+    end
+
+    resource :salmon do
+      desc 'Receive Salmon updates'
+
+      params do
+        requires :id, type: String, desc: 'Account ID'
+      end
+
+      post ':id' do
+        # todo
+      end
+    end
+  end
+end
diff --git a/app/api/mastodon/rest.rb b/app/api/mastodon/rest.rb
new file mode 100644
index 000000000..e011ab34d
--- /dev/null
+++ b/app/api/mastodon/rest.rb
@@ -0,0 +1,13 @@
+module Mastodon
+  class Rest < Grape::API
+    version 'v1', using: :path
+    format :json
+
+    resource :statuses do
+      desc 'Return a public timeline'
+      get :all do
+        present Status.all, with: Mastodon::Entities::Status
+      end
+    end
+  end
+end
diff --git a/app/assets/images/.keep b/app/assets/images/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/app/assets/images/.keep
diff --git a/app/assets/javascripts/application.js b/app/assets/javascripts/application.js
new file mode 100644
index 000000000..e07c5a830
--- /dev/null
+++ b/app/assets/javascripts/application.js
@@ -0,0 +1,16 @@
+// This is a manifest file that'll be compiled into application.js, which will include all the files
+// listed below.
+//
+// Any JavaScript/Coffee file within this directory, lib/assets/javascripts, vendor/assets/javascripts,
+// or any plugin's vendor/assets/javascripts directory can be referenced here using a relative path.
+//
+// It's not advisable to add code directly here, but if you do, it'll appear at the bottom of the
+// compiled file.
+//
+// Read Sprockets README (https://github.com/rails/sprockets#sprockets-directives) for details
+// about supported directives.
+//
+//= require jquery
+//= require jquery_ujs
+//= require turbolinks
+//= require_tree .
diff --git a/app/assets/stylesheets/application.css b/app/assets/stylesheets/application.css
new file mode 100644
index 000000000..f9cd5b348
--- /dev/null
+++ b/app/assets/stylesheets/application.css
@@ -0,0 +1,15 @@
+/*
+ * This is a manifest file that'll be compiled into application.css, which will include all the files
+ * listed below.
+ *
+ * Any CSS and SCSS file within this directory, lib/assets/stylesheets, vendor/assets/stylesheets,
+ * or any plugin's vendor/assets/stylesheets directory can be referenced here using a relative path.
+ *
+ * You're free to add application-wide styles to this file and they'll appear at the bottom of the
+ * compiled file so the styles you add here take precedence over styles defined in any styles
+ * defined in the other CSS/SCSS files in this directory. It is generally better to create a new
+ * file per style scope.
+ *
+ *= require_tree .
+ *= require_self
+ */
diff --git a/app/controllers/application_controller.rb b/app/controllers/application_controller.rb
new file mode 100644
index 000000000..d83690e1b
--- /dev/null
+++ b/app/controllers/application_controller.rb
@@ -0,0 +1,5 @@
+class ApplicationController < ActionController::Base
+  # Prevent CSRF attacks by raising an exception.
+  # For APIs, you may want to use :null_session instead.
+  protect_from_forgery with: :exception
+end
diff --git a/app/controllers/concerns/.keep b/app/controllers/concerns/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/app/controllers/concerns/.keep
diff --git a/app/helpers/application_helper.rb b/app/helpers/application_helper.rb
new file mode 100644
index 000000000..de6be7945
--- /dev/null
+++ b/app/helpers/application_helper.rb
@@ -0,0 +1,2 @@
+module ApplicationHelper
+end
diff --git a/app/mailers/.keep b/app/mailers/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/app/mailers/.keep
diff --git a/app/models/.keep b/app/models/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/app/models/.keep
diff --git a/app/models/account.rb b/app/models/account.rb
new file mode 100644
index 000000000..c0b153794
--- /dev/null
+++ b/app/models/account.rb
@@ -0,0 +1,7 @@
+class Account < ActiveRecord::Base
+  has_many :statuses, inverse_of: :account
+
+  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
+end
diff --git a/app/models/concerns/.keep b/app/models/concerns/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/app/models/concerns/.keep
diff --git a/app/models/status.rb b/app/models/status.rb
new file mode 100644
index 000000000..a1278ccaa
--- /dev/null
+++ b/app/models/status.rb
@@ -0,0 +1,3 @@
+class Status < ActiveRecord::Base
+  belongs_to :account, inverse_of: :statuses
+end
diff --git a/app/services/fetch_feed_service.rb b/app/services/fetch_feed_service.rb
new file mode 100644
index 000000000..3b8efbe3b
--- /dev/null
+++ b/app/services/fetch_feed_service.rb
@@ -0,0 +1,5 @@
+class FetchFeedService
+  def call(account)
+    # todo
+  end
+end
diff --git a/app/services/follow_remote_user_service.rb b/app/services/follow_remote_user_service.rb
new file mode 100644
index 000000000..f3c0e68df
--- /dev/null
+++ b/app/services/follow_remote_user_service.rb
@@ -0,0 +1,60 @@
+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/process_feed_update_service.rb b/app/services/process_feed_update_service.rb
new file mode 100644
index 000000000..0585fad7a
--- /dev/null
+++ b/app/services/process_feed_update_service.rb
@@ -0,0 +1,20 @@
+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/views/layouts/application.html.erb b/app/views/layouts/application.html.erb
new file mode 100644
index 000000000..ff0d4c865
--- /dev/null
+++ b/app/views/layouts/application.html.erb
@@ -0,0 +1,14 @@
+<!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>