@ -1,8 +0,0 @@ | |||
module Mastodon | |||
class API < Grape::API | |||
rescue_from :all | |||
mount Mastodon::Ostatus | |||
mount Mastodon::Rest | |||
end | |||
end |
@ -1,54 +0,0 @@ | |||
module Mastodon | |||
module Entities | |||
class Account < Grape::Entity | |||
include ApplicationHelper | |||
expose :id | |||
expose :username | |||
expose :domain do |account| | |||
account.local? ? LOCAL_DOMAIN : account.domain | |||
end | |||
expose :display_name | |||
expose :note | |||
expose :url do |account| | |||
account.local? ? profile_url(name: account.username) : account.url | |||
end | |||
end | |||
class Status < Grape::Entity | |||
include ApplicationHelper | |||
format_with(:iso_timestamp) { |dt| dt.iso8601 } | |||
expose :id | |||
expose :uri do |status| | |||
status.local? ? unique_tag(status.stream_entry.created_at, status.stream_entry.activity_id, status.stream_entry.activity_type) : status.uri | |||
end | |||
expose :url do |status| | |||
status.local? ? status_url(name: status.account.username, id: status.id) : status.url | |||
end | |||
expose :text | |||
expose :in_reply_to_id | |||
expose :reblog_of_id | |||
expose :reblog, using: Mastodon::Entities::Status | |||
expose :account, using: Mastodon::Entities::Account | |||
with_options(format_with: :iso_timestamp) do | |||
expose :created_at | |||
expose :updated_at | |||
end | |||
end | |||
class StreamEntry < Grape::Entity | |||
expose :activity, using: Mastodon::Entities::Status | |||
end | |||
end | |||
end |
@ -1,62 +0,0 @@ | |||
module Mastodon | |||
class Ostatus < Grape::API | |||
format :txt | |||
before do | |||
@account = Account.find(params[:id]) | |||
end | |||
resource :subscriptions do | |||
helpers do | |||
include ApplicationHelper | |||
end | |||
desc 'Receive updates from an account' | |||
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']) | |||
ProcessFeedService.new.(body, @account) | |||
status 201 | |||
else | |||
status 202 | |||
end | |||
end | |||
desc 'Confirm PuSH subscription to an account' | |||
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 targeted to account' | |||
params do | |||
requires :id, type: String, desc: 'Account ID' | |||
end | |||
post ':id' do | |||
ProcessInteractionService.new.(request.body.read, @account) | |||
status 201 | |||
end | |||
end | |||
end | |||
end |
@ -1,44 +0,0 @@ | |||
module Mastodon | |||
class Rest < Grape::API | |||
version 'v1', using: :path | |||
format :json | |||
helpers do | |||
def current_user | |||
User.first | |||
end | |||
end | |||
resource :timelines do | |||
desc 'Return a public timeline' | |||
get :public do | |||
# todo | |||
end | |||
desc 'Return the home timeline of a logged in user' | |||
get :home do | |||
present current_user.timeline, with: Mastodon::Entities::StreamEntry | |||
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 |
@ -1,3 +0,0 @@ | |||
# 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/ |
@ -1,3 +0,0 @@ | |||
# 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/ |
@ -1,3 +0,0 @@ | |||
# 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/ |
@ -1,3 +0,0 @@ | |||
# 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/ |
@ -0,0 +1,39 @@ | |||
.card { | |||
display: flex; | |||
background: $primary-color; | |||
box-shadow: 4px 3px 0 rgba(0, 0, 0, 0.1); | |||
.bio { | |||
flex-grow: 1; | |||
} | |||
.name { | |||
font-size: 20px; | |||
line-height: 18px * 1.5; | |||
color: $quaternary-color; | |||
small { | |||
display: block; | |||
font-size: 14px; | |||
color: $quaternary-color; | |||
} | |||
} | |||
.avatar { | |||
width: 96px; | |||
float: left; | |||
margin-right: 10px; | |||
padding: 10px; | |||
padding-right: 0; | |||
padding-left: 9px; | |||
margin-top: -30px; | |||
img { | |||
width: 94px; | |||
height: 94px; | |||
display: block; | |||
border-radius: 5px; | |||
box-shadow: 4px 3px 0 rgba(0, 0, 0, 0.1); | |||
} | |||
} | |||
} |
@ -1,3 +1,3 @@ | |||
// Place all the styles related to the Atom controller here. | |||
// Place all the styles related to the API::Salmon controller here. | |||
// They will automatically be included in application.css. | |||
// You can use Sass (SCSS) here: http://sass-lang.com/ |
@ -1,3 +1,3 @@ | |||
// Place all the styles related to the XRD controller here. | |||
// Place all the styles related to the API::Subscriptions controller here. | |||
// They will automatically be included in application.css. | |||
// You can use Sass (SCSS) here: http://sass-lang.com/ |
@ -0,0 +1,16 @@ | |||
class AccountsController < ApplicationController | |||
before_action :set_account | |||
def show | |||
respond_to do |format| | |||
format.html | |||
format.atom | |||
end | |||
end | |||
private | |||
def set_account | |||
@account = Account.find_by!(username: params[:username], domain: nil) | |||
end | |||
end |
@ -0,0 +1,14 @@ | |||
class Api::SalmonController < ApplicationController | |||
before_action :set_account | |||
def update | |||
ProcessInteractionService.new.(request.body.read, @account) | |||
render nothing: true, status: 201 | |||
end | |||
private | |||
def set_account | |||
@account = Account.find(params[:id]) | |||
end | |||
end |
@ -0,0 +1,28 @@ | |||
class Api::SubscriptionsController < ApplicationController | |||
before_action :set_account | |||
def show | |||
if @account.subscription(api_subscription_url(@account.id)).valid?(params['hub.topic'], params['hub.verify_token']) | |||
render text: params['hub.challenge'], status: 200 | |||
else | |||
render nothing: true, status: 404 | |||
end | |||
end | |||
def update | |||
body = request.body.read | |||
if @account.subscription(api_subscription_url(@account.id)).verify(body, env['HTTP_X_HUB_SIGNATURE']) | |||
ProcessFeedService.new.(body, @account) | |||
render nothing: true, status: 201 | |||
else | |||
render nothing: true, status: 202 | |||
end | |||
end | |||
private | |||
def set_account | |||
@account = Account.find(params[:id]) | |||
end | |||
end |
@ -1,18 +0,0 @@ | |||
class AtomController < ApplicationController | |||
before_filter :set_format | |||
def user_stream | |||
@account = Account.find_by!(id: params[:id], domain: nil) | |||
end | |||
def entry | |||
@entry = StreamEntry.find(params[:id]) | |||
end | |||
private | |||
def set_format | |||
request.format = 'xml' | |||
response.headers['Content-Type'] = 'application/atom+xml' | |||
end | |||
end |
@ -1,17 +0,0 @@ | |||
class ProfileController < ApplicationController | |||
before_action :set_account | |||
def show | |||
end | |||
def entry | |||
@entry = @account.stream_entries.find(params[:id]) | |||
@type = @entry.activity_type.downcase | |||
end | |||
private | |||
def set_account | |||
@account = Account.find_by!(username: params[:name], domain: nil) | |||
end | |||
end |
@ -0,0 +1,23 @@ | |||
class StreamEntriesController < ApplicationController | |||
before_action :set_account | |||
before_action :set_stream_entry | |||
def show | |||
@type = @stream_entry.activity_type.downcase | |||
respond_to do |format| | |||
format.html | |||
format.atom | |||
end | |||
end | |||
private | |||
def set_account | |||
@account = Account.find_by!(username: params[:account_username], domain: nil) | |||
end | |||
def set_stream_entry | |||
@stream_entry = @account.stream_entries.find(params[:id]) | |||
end | |||
end |
@ -0,0 +1,3 @@ | |||
module AccountsHelper | |||
end |
@ -0,0 +1,2 @@ | |||
module Api::SalmonHelper | |||
end |
@ -0,0 +1,2 @@ | |||
module Api::SubscriptionsHelper | |||
end |
@ -1,4 +1,4 @@ | |||
module ProfileHelper | |||
module StreamEntriesHelper | |||
def display_name(account) | |||
account.display_name.blank? ? account.username : account.display_name | |||
end |
@ -1,9 +1,4 @@ | |||
class User < ActiveRecord::Base | |||
belongs_to :account, inverse_of: :user | |||
validates :account, presence: true | |||
def timeline | |||
StreamEntry.where(account_id: self.account.following, activity_type: 'Status').order('id desc') | |||
end | |||
end |
@ -1,3 +1,4 @@ | |||
class BaseService | |||
include RoutingHelper | |||
include ApplicationHelper | |||
end |
@ -1,2 +0,0 @@ | |||
- if status.reply? | |||
= link_to "In response to #{status.thread.account.acct}", status_url(status.thread), class: 'conversation-link' |
@ -1,7 +0,0 @@ | |||
= link_to profile_url(status.account), class: 'name' do | |||
%strong= display_name(status.account) | |||
= "@#{status.account.acct}" | |||
= link_to status_url(status), class: 'time' do | |||
%span{ title: status.created_at } | |||
= relative_time(status.created_at) |
@ -1,5 +0,0 @@ | |||
- content_for :header_tags do | |||
%link{ rel: 'alternate', type: 'application/atom+xml', href: atom_entry_url(id: @entry.id) }/ | |||
.activity-stream | |||
= render partial: @type, locals: { @type.to_sym => @entry.activity, include_threads: true, is_predecessor: false, is_successor: false } |
@ -0,0 +1,5 @@ | |||
- content_for :header_tags do | |||
%link{ rel: 'alternate', type: 'application/atom+xml', href: account_stream_entry_url(@account, @stream_entry, format: 'atom') }/ | |||
.activity-stream | |||
= render partial: @type, locals: { @type.to_sym => @stream_entry.activity, include_threads: true, is_predecessor: false, is_successor: false } |
@ -1,10 +1,10 @@ | |||
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.Alias profile_url(@account) | |||
xml.Link(rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: profile_url(@account)) | |||
xml.Alias url_for_target(@account) | |||
xml.Link(rel: 'http://webfinger.net/rel/profile-page', type: 'text/html', href: url_for_target(@account)) | |||
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: 'salmon', href: api_salmon_url(@account.id)) | |||
xml.Link(rel: 'magic-public-key', href: @magic_key) | |||
end | |||
end.to_xml |
@ -0,0 +1,17 @@ | |||
require 'rails_helper' | |||
RSpec.describe AccountsController, type: :controller do | |||
let(:alice) { Fabricate(:account, username: 'alice') } | |||
describe 'GET #show' do | |||
it 'returns 200' do | |||
get :show, username: alice.username | |||
expect(response).to have_http_status(:success) | |||
end | |||
it 'returns 200 with Atom' do | |||
get :show, username: alice.username, format: 'atom' | |||
expect(response).to have_http_status(:success) | |||
end | |||
end | |||
end |
@ -0,0 +1,7 @@ | |||
require 'rails_helper' | |||
RSpec.describe Api::SalmonController, type: :controller do | |||
describe 'POST #update' do | |||
pending | |||
end | |||
end |
@ -0,0 +1,11 @@ | |||
require 'rails_helper' | |||
RSpec.describe Api::SubscriptionsController, type: :controller do | |||
describe 'GET #show' do | |||
pending | |||
end | |||
describe 'POST #update' do | |||
pending | |||
end | |||
end |
@ -1,11 +0,0 @@ | |||
require 'rails_helper' | |||
RSpec.describe AtomController, type: :controller do | |||
describe 'GET #user_stream' do | |||
pending | |||
end | |||
describe 'GET #entry' do | |||
pending | |||
end | |||
end |
@ -1,11 +0,0 @@ | |||
require 'rails_helper' | |||
RSpec.describe ProfileController, type: :controller do | |||
describe 'GET #show' do | |||
pending | |||
end | |||
describe 'GET #entry' do | |||
pending | |||
end | |||
end |
@ -0,0 +1,18 @@ | |||
require 'rails_helper' | |||
RSpec.describe StreamEntriesController, type: :controller do | |||
let(:alice) { Fabricate(:account, username: 'alice') } | |||
let(:status) { Fabricate(:status, account: alice) } | |||
describe 'GET #show' do | |||
it 'returns 200 with HTML' do | |||
get :show, account_username: alice.username, id: status.stream_entry.id | |||
expect(response).to have_http_status(:success) | |||
end | |||
it 'returns 200 with Atom' do | |||
get :show, account_username: alice.username, id: status.stream_entry.id, format: 'atom' | |||
expect(response).to have_http_status(:success) | |||
end | |||
end | |||
end |
@ -0,0 +1,15 @@ | |||
require 'rails_helper' | |||
# Specs in this file have access to a helper object that includes | |||
# the AccountsHelper. For example: | |||
# | |||
# describe AccountsHelper 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 AccountsHelper, type: :helper do | |||
pending "add some examples to (or delete) #{__FILE__}" | |||
end |
@ -0,0 +1,15 @@ | |||
require 'rails_helper' | |||
# Specs in this file have access to a helper object that includes | |||
# the Api::SalmonHelper. For example: | |||
# | |||
# describe Api::SalmonHelper 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 Api::SalmonHelper, type: :helper do | |||
pending "add some examples to (or delete) #{__FILE__}" | |||
end |
@ -0,0 +1,15 @@ | |||
require 'rails_helper' | |||
# Specs in this file have access to a helper object that includes | |||
# the Api::SubscriptionsHelper. For example: | |||
# | |||
# describe Api::SubscriptionsHelper 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 Api::SubscriptionsHelper, type: :helper do | |||
pending "add some examples to (or delete) #{__FILE__}" | |||
end |
@ -1,6 +1,6 @@ | |||
require 'rails_helper' | |||
RSpec.describe AtomHelper, type: :helper do | |||
RSpec.describe AtomBuilderHelper, type: :helper do | |||
describe '#stream_updated_at' do | |||
pending | |||
end |
@ -1,6 +1,6 @@ | |||
require 'rails_helper' | |||
RSpec.describe ProfileHelper, type: :helper do | |||
RSpec.describe StreamEntriesHelper, type: :helper do | |||
describe '#display_name' do | |||
pending | |||
end |