Move timelines API from statuses to its own controller, add a check for
resources that require a user context vs those that don't (such as public timeline) /api/v1/statuses/public -> /api/v1/timelines/public /api/v1/statuses/home -> /api/v1/timelines/home /api/v1/statuses/mentions -> /api/v1/timelines/mentions /api/v1/statuses/tag/:tag -> /api/v1/timelines/tag/:tag
This commit is contained in:
parent
86574ea524
commit
9aecc0f48a
|
@ -73,7 +73,7 @@ export function refreshTimeline(timeline, replace = false, id = null) {
|
|||
path = `${path}/${id}`
|
||||
}
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${path}${params}`).then(function (response) {
|
||||
api(getState).get(`/api/v1/timelines/${path}${params}`).then(function (response) {
|
||||
dispatch(refreshTimelineSuccess(timeline, response.data, replace));
|
||||
}).catch(function (error) {
|
||||
dispatch(refreshTimelineFail(timeline, error));
|
||||
|
@ -101,7 +101,7 @@ export function expandTimeline(timeline, id = null) {
|
|||
path = `${path}/${id}`
|
||||
}
|
||||
|
||||
api(getState).get(`/api/v1/statuses/${path}?max_id=${lastId}`).then(response => {
|
||||
api(getState).get(`/api/v1/timelines/${path}?max_id=${lastId}`).then(response => {
|
||||
dispatch(expandTimelineSuccess(timeline, response.data));
|
||||
}).catch(error => {
|
||||
dispatch(expandTimelineFail(timeline, error));
|
||||
|
|
|
@ -1,8 +1,9 @@
|
|||
class Api::V1::AccountsController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock]
|
||||
before_action -> { doorkeeper_authorize! :follow }, only: [:follow, :unfollow, :block, :unblock]
|
||||
|
||||
before_action :require_user!, except: [:show, :following, :followers, :statuses]
|
||||
before_action :set_account, except: [:verify_credentials, :suggestions]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def show
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
class Api::V1::FollowsController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :follow }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
class Api::V1::MediaController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :write }
|
||||
before_action :require_user!
|
||||
|
||||
respond_to :json
|
||||
|
||||
def create
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
class Api::V1::StatusesController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :read }, except: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite]
|
||||
before_action -> { doorkeeper_authorize! :write }, only: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite]
|
||||
|
||||
before_action :require_user!, except: [:show, :context, :reblogged_by, :favourited_by]
|
||||
before_action :set_status, only: [:show, :context, :reblogged_by, :favourited_by]
|
||||
|
||||
respond_to :json
|
||||
|
@ -56,37 +56,6 @@ class Api::V1::StatusesController < ApiController
|
|||
render action: :show
|
||||
end
|
||||
|
||||
def home
|
||||
@statuses = Feed.new(:home, current_user.account).get(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def mentions
|
||||
@statuses = Feed.new(:mentions, current_user.account).get(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def public
|
||||
@statuses = Status.as_public_timeline(current_user.account).paginate_by_max_id(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def tag
|
||||
@tag = Tag.find_by(name: params[:id].downcase)
|
||||
|
||||
if @tag.nil?
|
||||
@statuses = []
|
||||
else
|
||||
@statuses = Status.as_tag_timeline(@tag, current_user.account).paginate_by_max_id(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
end
|
||||
|
||||
render action: :index
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_status
|
||||
|
|
37
app/controllers/api/v1/timelines_controller.rb
Normal file
37
app/controllers/api/v1/timelines_controller.rb
Normal file
|
@ -0,0 +1,37 @@
|
|||
class Api::V1::TimelinesController < ApiController
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!, only: [:home, :mentions]
|
||||
|
||||
respond_to :json
|
||||
|
||||
def home
|
||||
@statuses = Feed.new(:home, current_account).get(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def mentions
|
||||
@statuses = Feed.new(:mentions, current_account).get(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def public
|
||||
@statuses = Status.as_public_timeline(current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
render action: :index
|
||||
end
|
||||
|
||||
def tag
|
||||
@tag = Tag.find_by(name: params[:id].downcase)
|
||||
|
||||
if @tag.nil?
|
||||
@statuses = []
|
||||
else
|
||||
@statuses = Status.as_tag_timeline(@tag, current_account).paginate_by_max_id(20, params[:max_id], params[:since_id]).to_a
|
||||
set_maps(@statuses)
|
||||
end
|
||||
|
||||
render action: :index
|
||||
end
|
||||
end
|
|
@ -60,6 +60,14 @@ class ApiController < ApplicationController
|
|||
|
||||
def current_user
|
||||
super || current_resource_owner
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
nil
|
||||
end
|
||||
|
||||
def require_user!
|
||||
current_resource_owner
|
||||
rescue ActiveRecord::RecordNotFound
|
||||
render json: { error: 'This method requires an authenticated user' }, status: 422
|
||||
end
|
||||
|
||||
def render_empty
|
||||
|
@ -67,8 +75,14 @@ class ApiController < ApplicationController
|
|||
end
|
||||
|
||||
def set_maps(statuses)
|
||||
if current_account.nil?
|
||||
@reblogs_map = {}
|
||||
@favourites_map = {}
|
||||
return
|
||||
end
|
||||
|
||||
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact.uniq
|
||||
@reblogs_map = Status.reblogs_map(status_ids, current_user.account)
|
||||
@favourites_map = Status.favourites_map(status_ids, current_user.account)
|
||||
@reblogs_map = Status.reblogs_map(status_ids, current_account)
|
||||
@favourites_map = Status.favourites_map(status_ids, current_account)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -95,23 +95,29 @@ class Status < ApplicationRecord
|
|||
where(id: Mention.where(account: account).pluck(:status_id)).with_includes.with_counters
|
||||
end
|
||||
|
||||
def as_public_timeline(account)
|
||||
joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.reblog_of_id = reblogs.id')
|
||||
def as_public_timeline(account = nil)
|
||||
query = joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.reblog_of_id = reblogs.id')
|
||||
.joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
|
||||
.where('accounts.silenced = FALSE')
|
||||
.where('(reblogs.account_id IS NULL OR reblogs.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)) AND statuses.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)', account.id, account.id)
|
||||
.with_includes
|
||||
.with_counters
|
||||
|
||||
unless account.nil?
|
||||
query = query.where('(reblogs.account_id IS NULL OR reblogs.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)) AND statuses.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)', account.id, account.id)
|
||||
end
|
||||
|
||||
def as_tag_timeline(tag, account)
|
||||
tag.statuses
|
||||
query.with_includes.with_counters
|
||||
end
|
||||
|
||||
def as_tag_timeline(tag, account = nil)
|
||||
query = tag.statuses
|
||||
.joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.reblog_of_id = reblogs.id')
|
||||
.joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
|
||||
.where('accounts.silenced = FALSE')
|
||||
.where('(reblogs.account_id IS NULL OR reblogs.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)) AND statuses.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)', account.id, account.id)
|
||||
.with_includes
|
||||
.with_counters
|
||||
|
||||
unless account.nil?
|
||||
query = query.where('(reblogs.account_id IS NULL OR reblogs.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)) AND statuses.account_id NOT IN (SELECT target_account_id FROM blocks WHERE account_id = ?)', account.id, account.id)
|
||||
end
|
||||
|
||||
query.with_includes.with_counters
|
||||
end
|
||||
|
||||
def favourites_map(status_ids, account_id)
|
||||
|
|
2
app/views/api/v1/timelines/index.rabl
Normal file
2
app/views/api/v1/timelines/index.rabl
Normal file
|
@ -0,0 +1,2 @@
|
|||
collection @statuses
|
||||
extends('api/v1/statuses/show')
|
|
@ -55,13 +55,6 @@ Rails.application.routes.draw do
|
|||
# JSON / REST API
|
||||
namespace :v1 do
|
||||
resources :statuses, only: [:create, :show, :destroy] do
|
||||
collection do
|
||||
get :home
|
||||
get :mentions
|
||||
get :public
|
||||
get '/tag/:id', action: :tag
|
||||
end
|
||||
|
||||
member do
|
||||
get :context
|
||||
get :reblogged_by
|
||||
|
@ -74,6 +67,15 @@ Rails.application.routes.draw do
|
|||
end
|
||||
end
|
||||
|
||||
resources :timelines, only: [] do
|
||||
collection do
|
||||
get :home
|
||||
get :mentions
|
||||
get :public
|
||||
get '/tag/:id', action: :tag
|
||||
end
|
||||
end
|
||||
|
||||
resources :follows, only: [:create]
|
||||
resources :media, only: [:create]
|
||||
resources :apps, only: [:create]
|
||||
|
|
|
@ -59,38 +59,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
|
|||
end
|
||||
end
|
||||
|
||||
describe 'GET #home' do
|
||||
it 'returns http success' do
|
||||
get :home
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #mentions' do
|
||||
it 'returns http success' do
|
||||
get :mentions
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #public' do
|
||||
it 'returns http success' do
|
||||
get :public
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #tag' do
|
||||
before do
|
||||
post :create, params: { status: 'It is a #test' }
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :tag, params: { id: 'test' }
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'POST #create' do
|
||||
before do
|
||||
post :create, params: { status: 'Hello world' }
|
||||
|
|
80
spec/controllers/api/v1/timelines_controller_spec.rb
Normal file
80
spec/controllers/api/v1/timelines_controller_spec.rb
Normal file
|
@ -0,0 +1,80 @@
|
|||
require 'rails_helper'
|
||||
|
||||
RSpec.describe Api::V1::TimelinesController, type: :controller do
|
||||
render_views
|
||||
|
||||
let(:user) { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
|
||||
|
||||
before do
|
||||
stub_request(:post, "https://pubsubhubbub.superfeedr.com/").to_return(:status => 200, :body => "", :headers => {})
|
||||
allow(controller).to receive(:doorkeeper_token) { token }
|
||||
end
|
||||
|
||||
context 'with a user context' do
|
||||
let(:token) { double acceptable?: true, resource_owner_id: user.id }
|
||||
|
||||
describe 'GET #home' do
|
||||
it 'returns http success' do
|
||||
get :home
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #mentions' do
|
||||
it 'returns http success' do
|
||||
get :mentions
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #public' do
|
||||
it 'returns http success' do
|
||||
get :public
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #tag' do
|
||||
before do
|
||||
PostStatusService.new.call(user.account, 'It is a #test')
|
||||
end
|
||||
|
||||
it 'returns http success' do
|
||||
get :tag, params: { id: 'test' }
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
context 'without a user context' do
|
||||
let(:token) { double acceptable?: true, resource_owner_id: nil }
|
||||
|
||||
describe 'GET #home' do
|
||||
it 'returns http unprocessable entity' do
|
||||
get :home
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #mentions' do
|
||||
it 'returns http unprocessable entity' do
|
||||
get :mentions
|
||||
expect(response).to have_http_status(:unprocessable_entity)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #public' do
|
||||
it 'returns http success' do
|
||||
get :public
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
|
||||
describe 'GET #tag' do
|
||||
it 'returns http success' do
|
||||
get :tag, params: { id: 'test' }
|
||||
expect(response).to have_http_status(:success)
|
||||
end
|
||||
end
|
||||
end
|
||||
end
|
Reference in a new issue