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/:tagmaster
@@ -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,8 +1,8 @@ | |||
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 :set_status, only: [:show, :context, :reblogged_by, :favourited_by] | |||
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 | |||
@@ -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 | |||
query.with_includes.with_counters | |||
end | |||
def as_tag_timeline(tag, account) | |||
tag.statuses | |||
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) | |||
@@ -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' } | |||
@@ -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 |