@ -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. | // They will automatically be included in application.css. | ||||
// You can use Sass (SCSS) here: http://sass-lang.com/ | // 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. | // They will automatically be included in application.css. | ||||
// You can use Sass (SCSS) here: http://sass-lang.com/ | // 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) | def display_name(account) | ||||
account.display_name.blank? ? account.username : account.display_name | account.display_name.blank? ? account.username : account.display_name | ||||
end | end |
@ -1,9 +1,4 @@ | |||||
class User < ActiveRecord::Base | class User < ActiveRecord::Base | ||||
belongs_to :account, inverse_of: :user | belongs_to :account, inverse_of: :user | ||||
validates :account, presence: true | validates :account, presence: true | ||||
def timeline | |||||
StreamEntry.where(account_id: self.account.following, activity_type: 'Status').order('id desc') | |||||
end | |||||
end | end |
@ -1,3 +1,4 @@ | |||||
class BaseService | class BaseService | ||||
include RoutingHelper | |||||
include ApplicationHelper | include ApplicationHelper | ||||
end | 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| | Nokogiri::XML::Builder.new do |xml| | ||||
xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do | xml.XRD(xmlns: 'http://docs.oasis-open.org/ns/xri/xrd-1.0') do | ||||
xml.Subject @canonical_account_uri | 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: '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) | xml.Link(rel: 'magic-public-key', href: @magic_key) | ||||
end | end | ||||
end.to_xml | 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' | require 'rails_helper' | ||||
RSpec.describe AtomHelper, type: :helper do | |||||
RSpec.describe AtomBuilderHelper, type: :helper do | |||||
describe '#stream_updated_at' do | describe '#stream_updated_at' do | ||||
pending | pending | ||||
end | end |
@ -1,6 +1,6 @@ | |||||
require 'rails_helper' | require 'rails_helper' | ||||
RSpec.describe ProfileHelper, type: :helper do | |||||
RSpec.describe StreamEntriesHelper, type: :helper do | |||||
describe '#display_name' do | describe '#display_name' do | ||||
pending | pending | ||||
end | end |