mirror of
https://github.com/mastodon/mastodon.git
synced 2024-11-20 11:35:29 +01:00
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}`
|
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));
|
dispatch(refreshTimelineSuccess(timeline, response.data, replace));
|
||||||
}).catch(function (error) {
|
}).catch(function (error) {
|
||||||
dispatch(refreshTimelineFail(timeline, error));
|
dispatch(refreshTimelineFail(timeline, error));
|
||||||
@ -101,7 +101,7 @@ export function expandTimeline(timeline, id = null) {
|
|||||||
path = `${path}/${id}`
|
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));
|
dispatch(expandTimelineSuccess(timeline, response.data));
|
||||||
}).catch(error => {
|
}).catch(error => {
|
||||||
dispatch(expandTimelineFail(timeline, error));
|
dispatch(expandTimelineFail(timeline, error));
|
||||||
|
@ -1,8 +1,9 @@
|
|||||||
class Api::V1::AccountsController < ApiController
|
class Api::V1::AccountsController < ApiController
|
||||||
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock]
|
before_action -> { doorkeeper_authorize! :read }, except: [:follow, :unfollow, :block, :unblock]
|
||||||
before_action -> { doorkeeper_authorize! :follow }, only: [: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]
|
before_action :set_account, except: [:verify_credentials, :suggestions]
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def show
|
def show
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
class Api::V1::FollowsController < ApiController
|
class Api::V1::FollowsController < ApiController
|
||||||
before_action -> { doorkeeper_authorize! :follow }
|
before_action -> { doorkeeper_authorize! :follow }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -1,5 +1,7 @@
|
|||||||
class Api::V1::MediaController < ApiController
|
class Api::V1::MediaController < ApiController
|
||||||
before_action -> { doorkeeper_authorize! :write }
|
before_action -> { doorkeeper_authorize! :write }
|
||||||
|
before_action :require_user!
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
|
|
||||||
def create
|
def create
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
class Api::V1::StatusesController < ApiController
|
class Api::V1::StatusesController < ApiController
|
||||||
before_action -> { doorkeeper_authorize! :read }, except: [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite]
|
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 -> { 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]
|
before_action :set_status, only: [:show, :context, :reblogged_by, :favourited_by]
|
||||||
|
|
||||||
respond_to :json
|
respond_to :json
|
||||||
@ -56,37 +56,6 @@ class Api::V1::StatusesController < ApiController
|
|||||||
render action: :show
|
render action: :show
|
||||||
end
|
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
|
private
|
||||||
|
|
||||||
def set_status
|
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
|
def current_user
|
||||||
super || current_resource_owner
|
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
|
end
|
||||||
|
|
||||||
def render_empty
|
def render_empty
|
||||||
@ -67,8 +75,14 @@ class ApiController < ApplicationController
|
|||||||
end
|
end
|
||||||
|
|
||||||
def set_maps(statuses)
|
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
|
status_ids = statuses.flat_map { |s| [s.id, s.reblog_of_id] }.compact.uniq
|
||||||
@reblogs_map = Status.reblogs_map(status_ids, current_user.account)
|
@reblogs_map = Status.reblogs_map(status_ids, current_account)
|
||||||
@favourites_map = Status.favourites_map(status_ids, current_user.account)
|
@favourites_map = Status.favourites_map(status_ids, current_account)
|
||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
@ -95,23 +95,29 @@ class Status < ApplicationRecord
|
|||||||
where(id: Mention.where(account: account).pluck(:status_id)).with_includes.with_counters
|
where(id: Mention.where(account: account).pluck(:status_id)).with_includes.with_counters
|
||||||
end
|
end
|
||||||
|
|
||||||
def as_public_timeline(account)
|
def as_public_timeline(account = nil)
|
||||||
joins('LEFT OUTER JOIN statuses AS reblogs ON statuses.reblog_of_id = reblogs.id')
|
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')
|
.joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
|
||||||
.where('accounts.silenced = FALSE')
|
.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
|
unless account.nil?
|
||||||
.with_counters
|
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
|
end
|
||||||
|
|
||||||
def as_tag_timeline(tag, account)
|
query.with_includes.with_counters
|
||||||
tag.statuses
|
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 statuses AS reblogs ON statuses.reblog_of_id = reblogs.id')
|
||||||
.joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
|
.joins('LEFT OUTER JOIN accounts ON statuses.account_id = accounts.id')
|
||||||
.where('accounts.silenced = FALSE')
|
.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
|
unless account.nil?
|
||||||
.with_counters
|
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
|
end
|
||||||
|
|
||||||
def favourites_map(status_ids, account_id)
|
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
|
# JSON / REST API
|
||||||
namespace :v1 do
|
namespace :v1 do
|
||||||
resources :statuses, only: [:create, :show, :destroy] do
|
resources :statuses, only: [:create, :show, :destroy] do
|
||||||
collection do
|
|
||||||
get :home
|
|
||||||
get :mentions
|
|
||||||
get :public
|
|
||||||
get '/tag/:id', action: :tag
|
|
||||||
end
|
|
||||||
|
|
||||||
member do
|
member do
|
||||||
get :context
|
get :context
|
||||||
get :reblogged_by
|
get :reblogged_by
|
||||||
@ -74,6 +67,15 @@ Rails.application.routes.draw do
|
|||||||
end
|
end
|
||||||
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 :follows, only: [:create]
|
||||||
resources :media, only: [:create]
|
resources :media, only: [:create]
|
||||||
resources :apps, only: [:create]
|
resources :apps, only: [:create]
|
||||||
|
@ -59,38 +59,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
|
|||||||
end
|
end
|
||||||
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
|
describe 'POST #create' do
|
||||||
before do
|
before do
|
||||||
post :create, params: { status: 'Hello world' }
|
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
|
Loading…
Reference in New Issue
Block a user