Refactoring Grape API methods into normal controllers & other things
This commit is contained in:
		
							
								
								
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							
							
						
						
									
										1
									
								
								Gemfile
									
									
									
									
									
								
							| @@ -22,6 +22,7 @@ gem 'grape-entity' | ||||
| gem 'hashie-forbidden_attributes' | ||||
| gem 'paranoia', '~> 2.0' | ||||
| gem 'paperclip', '~> 4.3' | ||||
| gem 'backport_new_renderer' | ||||
|  | ||||
| gem 'http' | ||||
| gem 'addressable' | ||||
|   | ||||
| @@ -43,6 +43,8 @@ GEM | ||||
|       descendants_tracker (~> 0.0.4) | ||||
|       ice_nine (~> 0.11.0) | ||||
|       thread_safe (~> 0.3, >= 0.3.1) | ||||
|     backport_new_renderer (1.0.0) | ||||
|       rails | ||||
|     better_errors (2.1.1) | ||||
|       coderay (>= 1.0.0) | ||||
|       erubis (>= 2.6.6) | ||||
| @@ -320,6 +322,7 @@ PLATFORMS | ||||
|  | ||||
| DEPENDENCIES | ||||
|   addressable | ||||
|   backport_new_renderer | ||||
|   better_errors | ||||
|   binding_of_caller | ||||
|   coffee-rails (~> 4.1.0) | ||||
|   | ||||
| @@ -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/ | ||||
							
								
								
									
										39
									
								
								app/assets/stylesheets/accounts.scss
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										39
									
								
								app/assets/stylesheets/accounts.scss
									
									
									
									
									
										Normal file
									
								
							| @@ -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/ | ||||
| @@ -20,7 +20,7 @@ body { | ||||
| } | ||||
|  | ||||
| .container { | ||||
|   width: 800px; | ||||
|   width: 700px; | ||||
|   margin: 0 auto; | ||||
|   margin-top: 40px; | ||||
| } | ||||
| @@ -40,4 +40,5 @@ body { | ||||
|  | ||||
|  | ||||
| @import 'home'; | ||||
| @import 'profile'; | ||||
| @import 'accounts'; | ||||
| @import 'stream_entries'; | ||||
|   | ||||
| @@ -1,59 +1,32 @@ | ||||
| .card { | ||||
|   display: flex; | ||||
|   background: $darker-background-color; | ||||
|   border: 1px solid darken($darker-background-color, 15%); | ||||
|   box-shadow: 4px 3px 0 rgba(0, 0, 0, 0.1); | ||||
| 
 | ||||
|   .bio { | ||||
|     flex-grow: 1; | ||||
|   } | ||||
| 
 | ||||
|   .name { | ||||
|     font-size: 24px; | ||||
|     line-height: 18px * 1.5; | ||||
|     color: $text-color; | ||||
| 
 | ||||
|     small { | ||||
|       display: block; | ||||
|       font-size: 14px; | ||||
|       color: $lighter-text-color; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .avatar { | ||||
|     width: 96px; | ||||
|     float: left; | ||||
|     margin-right: 10px; | ||||
|     padding: 10px; | ||||
|     padding-left: 9px; | ||||
|     margin-top: -30px; | ||||
| 
 | ||||
|     img { | ||||
|       width: 94px; | ||||
|       height: 94px; | ||||
|       display: block; | ||||
|       border: 2px solid $lighter-text-color; | ||||
|       border-radius: 5px; | ||||
|     } | ||||
|   } | ||||
| } | ||||
| 
 | ||||
| .activity-stream { | ||||
|   clear: both; | ||||
|   box-shadow: 4px 3px 0 rgba(0, 0, 0, 0.1); | ||||
| 
 | ||||
|   .entry { | ||||
|     border-bottom: 1px solid darken($background-color, 10%); | ||||
|     border-bottom: 1px solid $darker-background-color; | ||||
|     background: $background-color; | ||||
|     border-left: 2px solid $primary-color; | ||||
| 
 | ||||
|     &.entry-reblog { | ||||
|       border-left: 2px solid $tertiary-color; | ||||
| 
 | ||||
|       .content { | ||||
|         a { | ||||
|           color: $tertiary-color; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &.entry-predecessor, &.entry-successor { | ||||
|       border-left: 2px solid $lighter-text-color; | ||||
|       background: darken($background-color, 5%); | ||||
| 
 | ||||
|       .content { | ||||
|         a { | ||||
|           color: $lighter-text-color; | ||||
|         } | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     &.entry-follow, &.entry-favourite { | ||||
| @@ -92,9 +65,10 @@ | ||||
|   } | ||||
| 
 | ||||
|   .header { | ||||
|     margin-bottom: 10px; | ||||
|     margin-bottom: 5px; | ||||
|     padding: 10px; | ||||
|     padding-bottom: 0; | ||||
|     padding-left: 8px; | ||||
| 
 | ||||
|     .name { | ||||
|       text-decoration: none; | ||||
| @@ -113,7 +87,7 @@ | ||||
|   } | ||||
| 
 | ||||
|   .pre-header { | ||||
|     border-bottom: 1px solid darken($background-color, 10%); | ||||
|     border-bottom: 1px solid darken($background-color, 5%); | ||||
|     color: $tertiary-color; | ||||
|     padding: 5px 10px; | ||||
|     padding-left: 8px; | ||||
| @@ -131,9 +105,10 @@ | ||||
|   } | ||||
| 
 | ||||
|   .content { | ||||
|     font-size: 16px; | ||||
|     font-size: 14px; | ||||
|     padding: 0 10px; | ||||
|     padding-left: 8px; | ||||
|     padding-bottom: 25px; | ||||
| 
 | ||||
|     a { | ||||
|       color: $primary-color; | ||||
| @@ -153,24 +128,4 @@ | ||||
|       text-decoration: underline; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   .counters { | ||||
|     margin-top: 15px; | ||||
|     color: $lighter-text-color; | ||||
|     cursor: default; | ||||
|     padding: 10px; | ||||
|     padding-top: 0; | ||||
| 
 | ||||
|     .counter { | ||||
|       display: inline-block; | ||||
|       margin-right: 10px; | ||||
|       color: $lighter-text-color; | ||||
|     } | ||||
| 
 | ||||
|     .conversation-link { | ||||
|       color: $primary-color; | ||||
|       text-decoration: underline; | ||||
|       float: right; | ||||
|     } | ||||
|   } | ||||
| } | ||||
							
								
								
									
										16
									
								
								app/controllers/accounts_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										16
									
								
								app/controllers/accounts_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										14
									
								
								app/controllers/api/salmon_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								app/controllers/api/salmon_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										28
									
								
								app/controllers/api/subscriptions_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										28
									
								
								app/controllers/api/subscriptions_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										23
									
								
								app/controllers/stream_entries_controller.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								app/controllers/stream_entries_controller.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -9,6 +9,8 @@ class XrdController < ApplicationController | ||||
|     @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) | ||||
|   rescue ActiveRecord::RecordNotFound | ||||
|     render nothing: true, status: 404 | ||||
|   end | ||||
|  | ||||
|   private | ||||
|   | ||||
							
								
								
									
										3
									
								
								app/helpers/accounts_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3
									
								
								app/helpers/accounts_helper.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,3 @@ | ||||
| module AccountsHelper | ||||
|  | ||||
| end | ||||
							
								
								
									
										2
									
								
								app/helpers/api/salmon_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								app/helpers/api/salmon_helper.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| module Api::SalmonHelper | ||||
| end | ||||
							
								
								
									
										2
									
								
								app/helpers/api/subscriptions_helper.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										2
									
								
								app/helpers/api/subscriptions_helper.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,2 @@ | ||||
| module Api::SubscriptionsHelper | ||||
| end | ||||
| @@ -1,6 +1,4 @@ | ||||
| module ApplicationHelper | ||||
|   include RoutingHelper | ||||
|  | ||||
|   def unique_tag(date, id, type) | ||||
|     "tag:#{LOCAL_DOMAIN},#{date.strftime('%Y-%m-%d')}:objectId=#{id}:objectType=#{type}" | ||||
|   end | ||||
| @@ -13,24 +11,4 @@ module ApplicationHelper | ||||
|   def local_id?(id) | ||||
|     id.start_with?("tag:#{LOCAL_DOMAIN}") | ||||
|   end | ||||
|  | ||||
|   def subscription_url(account) | ||||
|     add_base_url_prefix subscriptions_path(id: account.id, format: '') | ||||
|   end | ||||
|  | ||||
|   def salmon_url(account) | ||||
|     add_base_url_prefix salmon_path(id: account.id, format: '') | ||||
|   end | ||||
|  | ||||
|   def profile_url(account) | ||||
|     account.local? ? super(name: account.username) : account.url | ||||
|   end | ||||
|  | ||||
|   def status_url(status) | ||||
|     status.local? ? super(name: status.account.username, id: status.stream_entry.id) : status.url | ||||
|   end | ||||
|  | ||||
|   def add_base_url_prefix(suffix) | ||||
|     File.join(root_url, "api", suffix) | ||||
|   end | ||||
| end | ||||
|   | ||||
| @@ -1,4 +1,4 @@ | ||||
| module AtomHelper | ||||
| module AtomBuilderHelper | ||||
|   def stream_updated_at | ||||
|     @account.stream_entries.last ? (@account.updated_at > @account.stream_entries.last.created_at ? @account.updated_at : @account.stream_entries.last.created_at) : @account.updated_at | ||||
|   end | ||||
| @@ -97,10 +97,10 @@ module AtomHelper | ||||
|     xml['thr'].send('in-reply-to', { ref: uri, href: url, type: 'text/html' }) | ||||
|   end | ||||
| 
 | ||||
|   def disambiguate_uri(target) | ||||
|   def uri_for_target(target) | ||||
|     if target.local? | ||||
|       if target.object_type == :person | ||||
|         profile_url(target) | ||||
|         account_url(target) | ||||
|       else | ||||
|         unique_tag(target.stream_entry.created_at, target.stream_entry.activity_id, target.stream_entry.activity_type) | ||||
|       end | ||||
| @@ -109,12 +109,12 @@ module AtomHelper | ||||
|     end | ||||
|   end | ||||
| 
 | ||||
|   def disambiguate_url(target) | ||||
|   def url_for_target(target) | ||||
|     if target.local? | ||||
|       if target.object_type == :person | ||||
|         profile_url(target) | ||||
|         account_url(target) | ||||
|       else | ||||
|         status_url(target) | ||||
|         account_stream_entry_url(target.account, target.stream_entry) | ||||
|       end | ||||
|     else | ||||
|       target.url | ||||
| @@ -122,13 +122,13 @@ module AtomHelper | ||||
|   end | ||||
| 
 | ||||
|   def link_mention(xml, account) | ||||
|     xml.link(rel: 'mentioned', href: disambiguate_uri(account)) | ||||
|     xml.link(rel: 'mentioned', href: uri_for_target(account)) | ||||
|   end | ||||
| 
 | ||||
|   def link_avatar(xml, account) | ||||
|     xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '300', 'media:height' =>'300', 'href' => asset_url(account.avatar.url(:large))) | ||||
|     xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '96', 'media:height' =>'96', 'href' => asset_url(account.avatar.url(:medium))) | ||||
|     xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '48', 'media:height' =>'48', 'href' => asset_url(account.avatar.url(:small))) | ||||
|     xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '300', 'media:height' =>'300', 'href' => asset_url(account.avatar.url(:large, false))) | ||||
|     xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '96', 'media:height' =>'96', 'href' => asset_url(account.avatar.url(:medium, false))) | ||||
|     xml.link('rel' => 'avatar', 'type' => account.avatar_content_type, 'media:width' => '48', 'media:height' =>'48', 'href' => asset_url(account.avatar.url(:small, false))) | ||||
|   end | ||||
| 
 | ||||
|   def logo(xml, url) | ||||
| @@ -137,10 +137,10 @@ module AtomHelper | ||||
| 
 | ||||
|   def include_author(xml, account) | ||||
|     object_type      xml, :person | ||||
|     uri              xml, profile_url(account) | ||||
|     uri              xml, url_for_target(account) | ||||
|     name             xml, account.username | ||||
|     summary          xml, account.note | ||||
|     link_alternate   xml, profile_url(account) | ||||
|     link_alternate   xml, url_for_target(account) | ||||
|     link_avatar      xml, account | ||||
|     portable_contact xml, account | ||||
|   end | ||||
| @@ -152,20 +152,20 @@ module AtomHelper | ||||
|     title        xml, stream_entry.title | ||||
|     content      xml, stream_entry.content | ||||
|     verb         xml, stream_entry.verb | ||||
|     link_self    xml, atom_entry_url(id: stream_entry.id) | ||||
|     link_self    xml, account_stream_entry_url(stream_entry.account, stream_entry, format: 'atom') | ||||
|     object_type  xml, stream_entry.object_type | ||||
| 
 | ||||
|     # Comments need thread element | ||||
|     if stream_entry.threaded? | ||||
|       in_reply_to xml, disambiguate_uri(stream_entry.thread), disambiguate_url(stream_entry.thread) | ||||
|       in_reply_to xml, uri_for_target(stream_entry.thread), url_for_target(stream_entry.thread) | ||||
|     end | ||||
| 
 | ||||
|     if stream_entry.targeted? | ||||
|       target(xml) do | ||||
|         object_type    xml, stream_entry.target.object_type | ||||
|         simple_id      xml, disambiguate_uri(stream_entry.target) | ||||
|         simple_id      xml, uri_for_target(stream_entry.target) | ||||
|         title          xml, stream_entry.target.title | ||||
|         link_alternate xml, disambiguate_url(stream_entry.target) | ||||
|         link_alternate xml, url_for_target(stream_entry.target) | ||||
| 
 | ||||
|         # People have summary and portable contacts information | ||||
|         if stream_entry.target.object_type == :person | ||||
| @@ -1,4 +1,4 @@ | ||||
| module ProfileHelper | ||||
| module StreamEntriesHelper | ||||
|   def display_name(account) | ||||
|     account.display_name.blank? ? account.username : account.display_name | ||||
|   end | ||||
| @@ -76,6 +76,10 @@ class Account < ActiveRecord::Base | ||||
|     @avatar_remote_url = url | ||||
|   end | ||||
|  | ||||
|   def to_param | ||||
|     self.username | ||||
|   end | ||||
|  | ||||
|   before_create do | ||||
|     if local? | ||||
|       keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 1024 : 2048) | ||||
|   | ||||
| @@ -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 | ||||
|   | ||||
| @@ -38,7 +38,7 @@ class FollowRemoteAccountService < BaseService | ||||
|       account.secret       = SecureRandom.hex | ||||
|       account.verify_token = SecureRandom.hex | ||||
|  | ||||
|       subscription = account.subscription(subscription_url(account)) | ||||
|       subscription = account.subscription(api_subscription_url(account.id)) | ||||
|       subscription.subscribe | ||||
|  | ||||
|       account.save! | ||||
|   | ||||
| @@ -9,7 +9,7 @@ class FollowService < BaseService | ||||
|  | ||||
|     follow = source_account.follow!(target_account) | ||||
|     send_interaction_service.(follow.stream_entry, target_account) | ||||
|     source_account.ping!(atom_user_stream_url(id: source_account.id), [HUB_URL]) | ||||
|     source_account.ping!(account_url(account, format: 'atom'), [HUB_URL]) | ||||
|   end | ||||
|  | ||||
|   private | ||||
|   | ||||
| @@ -7,7 +7,7 @@ class PostStatusService < BaseService | ||||
|   def call(account, text, in_reply_to = nil) | ||||
|     status = account.statuses.create!(text: text, thread: in_reply_to) | ||||
|     process_mentions_service.(status) | ||||
|     account.ping!(atom_user_stream_url(id: account.id), [HUB_URL]) | ||||
|     account.ping!(account_url(account, format: 'atom'), [HUB_URL]) | ||||
|     status | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -43,7 +43,7 @@ class ProcessInteractionService < BaseService | ||||
|   end | ||||
|  | ||||
|   def mentions_account?(xml, account) | ||||
|     xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]').each { |mention_link| return true if mention_link.attribute('href').value == profile_url(account) } | ||||
|     xml.xpath('/xmlns:entry/xmlns:link[@rel="mentioned"]').each { |mention_link| return true if mention_link.attribute('href').value == url_for_target(account) } | ||||
|     false | ||||
|   end | ||||
|  | ||||
|   | ||||
| @@ -5,7 +5,7 @@ class ReblogService < BaseService | ||||
|   # @return [Status] | ||||
|   def call(account, reblogged_status) | ||||
|     reblog = account.statuses.create!(reblog: reblogged_status, text: '') | ||||
|     account.ping!(atom_user_stream_url(id: account.id), [HUB_URL]) | ||||
|     account.ping!(account_url(account, format: 'atom'), [HUB_URL]) | ||||
|     return reblog if reblogged_status.local? | ||||
|     send_interaction_service.(reblog.stream_entry, reblogged_status.account) | ||||
|     reblog | ||||
|   | ||||
| @@ -1,5 +1,5 @@ | ||||
| class SendInteractionService < BaseService | ||||
|   include AtomHelper | ||||
|   include AtomBuilderHelper | ||||
|  | ||||
|   # Send an Atom representation of an interaction to a remote Salmon endpoint | ||||
|   # @param [StreamEntry] stream_entry | ||||
|   | ||||
| @@ -1,6 +1,6 @@ | ||||
| Nokogiri::XML::Builder.new do |xml| | ||||
|   feed(xml) do | ||||
|     simple_id  xml, atom_user_stream_url(id: @account.id) | ||||
|     simple_id  xml, account_url(@account, format: 'atom') | ||||
|     title      xml, @account.display_name | ||||
|     subtitle   xml, @account.note | ||||
|     updated_at xml, stream_updated_at | ||||
| @@ -10,10 +10,10 @@ Nokogiri::XML::Builder.new do |xml| | ||||
|       include_author xml, @account | ||||
|     end | ||||
| 
 | ||||
|     link_alternate xml, profile_url(@account) | ||||
|     link_self      xml, atom_user_stream_url(id: @account.id) | ||||
|     link_alternate xml, url_for_target(@account) | ||||
|     link_self      xml, account_url(@account, format: 'atom') | ||||
|     link_hub       xml, HUB_URL | ||||
|     link_salmon    xml, salmon_url(@account) | ||||
|     link_salmon    xml, api_salmon_url(@account.id) | ||||
| 
 | ||||
|     @account.stream_entries.order('id desc').each do |stream_entry| | ||||
|       entry(xml, false) do | ||||
| @@ -1,6 +1,6 @@ | ||||
| - content_for :header_tags do | ||||
|   %link{ rel: 'salmon', href: salmon_url(@account) }/ | ||||
|   %link{ rel: 'alternate', type: 'application/atom+xml', href: atom_user_stream_url(id: @account.id) }/ | ||||
|   %link{ rel: 'salmon', href: api_salmon_url(@account.id) }/ | ||||
|   %link{ rel: 'alternate', type: 'application/atom+xml', href: account_url(@account, format: 'atom') }/ | ||||
| 
 | ||||
| .card | ||||
|   .avatar= image_tag @account.avatar.url(:medium) | ||||
| @@ -11,4 +11,4 @@ | ||||
| 
 | ||||
| .activity-stream | ||||
|   - @account.statuses.order('id desc').each do |status| | ||||
|     = render partial: 'status', locals: { status: status, include_threads: false, is_successor: false, is_predecessor: false } | ||||
|     = render partial: 'stream_entries/status', locals: { status: status, include_threads: false, is_successor: false, is_predecessor: false } | ||||
| @@ -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 } | ||||
| @@ -6,17 +6,23 @@ | ||||
|     .pre-header | ||||
|       %i.fa.fa-retweet | ||||
|       Shared by | ||||
|       = link_to display_name(status.account), profile_url(status.account), class: 'name' | ||||
|       = link_to display_name(status.account), url_for_target(status.account), class: 'name' | ||||
| 
 | ||||
|   .entry__container | ||||
|     .avatar | ||||
|       = image_tag avatar_for_status_url(status) | ||||
| 
 | ||||
|     .entry__container__container | ||||
|       .header | ||||
|         = render partial: 'status_header', locals: { status: status.reblog? ? status.reblog : status } | ||||
|         = link_to url_for_target(status.reblog? ? status.reblog.account : status.account), class: 'name' do | ||||
|           %strong= display_name(status.reblog? ? status.reblog.account : status.account) | ||||
|           = "@#{status.reblog? ? status.reblog.account.acct : status.account.acct}" | ||||
|         = link_to url_for_target(status.reblog? ? status.reblog : status), class: 'time' do | ||||
|           %span{ title: status.reblog? ? status.reblog.created_at : status.created_at } | ||||
|             = relative_time(status.reblog? ? status.reblog.created_at : status.created_at) | ||||
| 
 | ||||
|       .content | ||||
|         = status.content.html_safe | ||||
|       .counters | ||||
|         = render partial: 'status_footer', locals: { status: status.reblog? ? status.reblog : status } | ||||
| 
 | ||||
| - if include_threads | ||||
|   - status.replies.each do |status| | ||||
							
								
								
									
										5
									
								
								app/views/stream_entries/show.html.haml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								app/views/stream_entries/show.html.haml
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
|   | ||||
| @@ -2,12 +2,15 @@ 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/entries/:id', to: 'atom#entry',       as: :atom_entry | ||||
|   get 'atom/users/:id',   to: 'atom#user_stream', as: :atom_user_stream | ||||
|   get 'users/:name',      to: 'profile#show',     as: :profile | ||||
|   get 'users/:name/:id',  to: 'profile#entry',    as: :status | ||||
|   resources :accounts, path: 'users', only: [:show], param: :username do | ||||
|     resources :stream_entries, path: 'updates', only: [:show] | ||||
|   end | ||||
|  | ||||
|   mount Mastodon::API => '/api/' | ||||
|   namespace :api do | ||||
|     resources :subscriptions, only: [:show] | ||||
|     post '/subscriptions/:id', to: 'subscriptions#update' | ||||
|     post '/salmon/:id', to: 'salmon#update', as: :salmon | ||||
|   end | ||||
|  | ||||
|   root 'home#index' | ||||
| end | ||||
|   | ||||
							
								
								
									
										17
									
								
								spec/controllers/accounts_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								spec/controllers/accounts_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										7
									
								
								spec/controllers/api/salmon_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								spec/controllers/api/salmon_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,7 @@ | ||||
| require 'rails_helper' | ||||
|  | ||||
| RSpec.describe Api::SalmonController, type: :controller do | ||||
|   describe 'POST #update' do | ||||
|     pending | ||||
|   end | ||||
| end | ||||
							
								
								
									
										11
									
								
								spec/controllers/api/subscriptions_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										11
									
								
								spec/controllers/api/subscriptions_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -2,6 +2,9 @@ require 'rails_helper' | ||||
|  | ||||
| RSpec.describe HomeController, type: :controller do | ||||
|   describe 'GET #index' do | ||||
|     pending | ||||
|     it 'returns 200' do | ||||
|       get :index | ||||
|       expect(response).to have_http_status(:success) | ||||
|     end | ||||
|   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 | ||||
							
								
								
									
										18
									
								
								spec/controllers/stream_entries_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								spec/controllers/stream_entries_controller_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -2,10 +2,23 @@ require 'rails_helper' | ||||
|  | ||||
| RSpec.describe XrdController, type: :controller do | ||||
|   describe 'GET #host_meta' do | ||||
|     pending | ||||
|     it 'returns 200' do | ||||
|       get :host_meta | ||||
|       expect(response).to have_http_status(:success) | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe 'GET #webfinger' do | ||||
|     pending | ||||
|     let(:alice) { Fabricate(:account, username: 'alice') } | ||||
|  | ||||
|     it 'returns 200 when account can be found' do | ||||
|       get :webfinger, resource: "acct:#{alice.username}@anything.com" | ||||
|       expect(response).to have_http_status(:success) | ||||
|     end | ||||
|  | ||||
|     it 'returns 404 when account cannot be found' do | ||||
|       get :webfinger, resource: 'acct:not@existing.com' | ||||
|       expect(response).to have_http_status(:not_found) | ||||
|     end | ||||
|   end | ||||
| end | ||||
|   | ||||
							
								
								
									
										15
									
								
								spec/helpers/accounts_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								spec/helpers/accounts_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										15
									
								
								spec/helpers/api/salmon_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								spec/helpers/api/salmon_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
							
								
								
									
										15
									
								
								spec/helpers/api/subscriptions_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										15
									
								
								spec/helpers/api/subscriptions_helper_spec.rb
									
									
									
									
									
										Normal file
									
								
							| @@ -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 | ||||
| @@ -28,18 +28,4 @@ RSpec.describe ApplicationHelper, type: :helper do | ||||
|       expect(helper.local_id?('tag:foreign.tld;objectId=12:objectType=Status')).to be false | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe '#add_base_url_prefix' do | ||||
|     it 'returns full API URL from base to suffix' do | ||||
|       expect(helper.add_base_url_prefix('test')).to eql "#{root_url}api/test" | ||||
|     end | ||||
|   end | ||||
|  | ||||
|   describe '#profile_url' do | ||||
|     pending | ||||
|   end | ||||
|  | ||||
|   describe '#status_url' do | ||||
|     pending | ||||
|   end | ||||
| 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 | ||||
		Reference in New Issue
	
	Block a user