Browse Source

Move create/destroy actions for api/v1/statuses to namespace (#3678)

Each of mute, favourite, reblog has been updated to:

- Have a separate controller with just a create and destroy action
- Preserve historical route names to not break the API
- Mild refactoring to break up long methods
pull/3/head
Matt Jankowski 2 years ago
parent
commit
2925372ff4

+ 38
- 0
app/controllers/api/v1/statuses/favourites_controller.rb View File

@@ -0,0 +1,38 @@
1
+# frozen_string_literal: true
2
+
3
+class Api::V1::Statuses::FavouritesController < Api::BaseController
4
+  include Authorization
5
+
6
+  before_action -> { doorkeeper_authorize! :write }
7
+  before_action :require_user!
8
+
9
+  respond_to :json
10
+
11
+  def create
12
+    @status = favourited_status
13
+    render 'api/v1/statuses/show'
14
+  end
15
+
16
+  def destroy
17
+    @status = requested_status
18
+    @favourites_map = { @status.id => false }
19
+
20
+    UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
21
+
22
+    render 'api/v1/statuses/show'
23
+  end
24
+
25
+  private
26
+
27
+  def favourited_status
28
+    service_result.status.reload
29
+  end
30
+
31
+  def service_result
32
+    FavouriteService.new.call(current_user.account, requested_status)
33
+  end
34
+
35
+  def requested_status
36
+    Status.find(params[:status_id])
37
+  end
38
+end

+ 41
- 0
app/controllers/api/v1/statuses/mutes_controller.rb View File

@@ -0,0 +1,41 @@
1
+# frozen_string_literal: true
2
+
3
+class Api::V1::Statuses::MutesController < Api::BaseController
4
+  include Authorization
5
+
6
+  before_action -> { doorkeeper_authorize! :write }
7
+  before_action :require_user!
8
+  before_action :set_status
9
+  before_action :set_conversation
10
+
11
+  respond_to :json
12
+
13
+  def create
14
+    current_account.mute_conversation!(@conversation)
15
+    @mutes_map = { @conversation.id => true }
16
+
17
+    render 'api/v1/statuses/show'
18
+  end
19
+
20
+  def destroy
21
+    current_account.unmute_conversation!(@conversation)
22
+    @mutes_map = { @conversation.id => false }
23
+
24
+    render 'api/v1/statuses/show'
25
+  end
26
+
27
+  private
28
+
29
+  def set_status
30
+    @status = Status.find(params[:status_id])
31
+    authorize @status, :show?
32
+  rescue Mastodon::NotPermittedError
33
+    # Reraise in order to get a 404 instead of a 403 error code
34
+    raise ActiveRecord::RecordNotFound
35
+  end
36
+
37
+  def set_conversation
38
+    @conversation = @status.conversation
39
+    raise Mastodon::ValidationError if @conversation.nil?
40
+  end
41
+end

+ 35
- 0
app/controllers/api/v1/statuses/reblogs_controller.rb View File

@@ -0,0 +1,35 @@
1
+# frozen_string_literal: true
2
+
3
+class Api::V1::Statuses::ReblogsController < Api::BaseController
4
+  include Authorization
5
+
6
+  before_action -> { doorkeeper_authorize! :write }
7
+  before_action :require_user!
8
+
9
+  respond_to :json
10
+
11
+  def create
12
+    @status = ReblogService.new.call(current_user.account, status_for_reblog)
13
+    render 'api/v1/statuses/show'
14
+  end
15
+
16
+  def destroy
17
+    @status = status_for_destroy.reblog
18
+    @reblogs_map = { @status.id => false }
19
+
20
+    authorize status_for_destroy, :unreblog?
21
+    RemovalWorker.perform_async(status_for_destroy.id)
22
+
23
+    render 'api/v1/statuses/show'
24
+  end
25
+
26
+  private
27
+
28
+  def status_for_reblog
29
+    Status.find params[:status_id]
30
+  end
31
+
32
+  def status_for_destroy
33
+    current_user.account.statuses.where(reblog_of_id: params[:status_id]).first!
34
+  end
35
+end

+ 3
- 56
app/controllers/api/v1/statuses_controller.rb View File

@@ -3,11 +3,10 @@
3 3
 class Api::V1::StatusesController < Api::BaseController
4 4
   include Authorization
5 5
 
6
-  before_action :authorize_if_got_token, except:            [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite, :mute, :unmute]
7
-  before_action -> { doorkeeper_authorize! :write }, only:  [:create, :destroy, :reblog, :unreblog, :favourite, :unfavourite, :mute, :unmute]
6
+  before_action :authorize_if_got_token, except:            [:create, :destroy]
7
+  before_action -> { doorkeeper_authorize! :write }, only:  [:create, :destroy]
8 8
   before_action :require_user!, except:  [:show, :context, :card]
9
-  before_action :set_status, only:       [:show, :context, :card, :mute, :unmute]
10
-  before_action :set_conversation, only: [:mute, :unmute]
9
+  before_action :set_status, only:       [:show, :context, :card]
11 10
 
12 11
   respond_to :json
13 12
 
@@ -56,53 +55,6 @@ class Api::V1::StatusesController < Api::BaseController
56 55
     render_empty
57 56
   end
58 57
 
59
-  def reblog
60
-    @status = ReblogService.new.call(current_user.account, Status.find(params[:id]))
61
-    render :show
62
-  end
63
-
64
-  def unreblog
65
-    reblog       = Status.where(account_id: current_user.account, reblog_of_id: params[:id]).first!
66
-    @status      = reblog.reblog
67
-    @reblogs_map = { @status.id => false }
68
-
69
-    authorize reblog, :unreblog?
70
-
71
-    RemovalWorker.perform_async(reblog.id)
72
-
73
-    render :show
74
-  end
75
-
76
-  def favourite
77
-    @status = FavouriteService.new.call(current_user.account, Status.find(params[:id])).status.reload
78
-    render :show
79
-  end
80
-
81
-  def unfavourite
82
-    @status         = Status.find(params[:id])
83
-    @favourites_map = { @status.id => false }
84
-
85
-    UnfavouriteWorker.perform_async(current_user.account_id, @status.id)
86
-
87
-    render :show
88
-  end
89
-
90
-  def mute
91
-    current_account.mute_conversation!(@conversation)
92
-
93
-    @mutes_map = { @conversation.id => true }
94
-
95
-    render :show
96
-  end
97
-
98
-  def unmute
99
-    current_account.unmute_conversation!(@conversation)
100
-
101
-    @mutes_map = { @conversation.id => false }
102
-
103
-    render :show
104
-  end
105
-
106 58
   private
107 59
 
108 60
   def set_status
@@ -113,11 +65,6 @@ class Api::V1::StatusesController < Api::BaseController
113 65
     raise ActiveRecord::RecordNotFound
114 66
   end
115 67
 
116
-  def set_conversation
117
-    @conversation = @status.conversation
118
-    raise Mastodon::ValidationError if @conversation.nil?
119
-  end
120
-
121 68
   def status_params
122 69
     params.permit(:status, :in_reply_to_id, :sensitive, :spoiler_text, :visibility, media_ids: [])
123 70
   end

+ 10
- 11
config/routes.rb View File

@@ -129,22 +129,21 @@ Rails.application.routes.draw do
129 129
     namespace :v1 do
130 130
       resources :statuses, only: [:create, :show, :destroy] do
131 131
         scope module: :statuses do
132
-          with_options only: :index do
133
-            resources :reblogged_by, controller: :reblogged_by_accounts
134
-            resources :favourited_by, controller: :favourited_by_accounts
135
-          end
132
+          resources :reblogged_by, controller: :reblogged_by_accounts, only: :index
133
+          resources :favourited_by, controller: :favourited_by_accounts, only: :index
134
+          resource :reblog, only: :create
135
+          post :unreblog, to: 'reblogs#destroy'
136
+
137
+          resource :favourite, only: :create
138
+          post :unfavourite, to: 'favourites#destroy'
139
+
140
+          resource :mute, only: :create
141
+          post :unmute, to: 'mutes#destroy'
136 142
         end
137 143
 
138 144
         member do
139 145
           get :context
140 146
           get :card
141
-
142
-          post :reblog
143
-          post :unreblog
144
-          post :favourite
145
-          post :unfavourite
146
-          post :mute
147
-          post :unmute
148 147
         end
149 148
       end
150 149
 

+ 66
- 0
spec/controllers/api/v1/statuses/favourites_controller_spec.rb View File

@@ -0,0 +1,66 @@
1
+# frozen_string_literal: true
2
+
3
+require 'rails_helper'
4
+
5
+describe Api::V1::Statuses::FavouritesController do
6
+  render_views
7
+
8
+  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
9
+  let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
10
+  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
11
+
12
+  context 'with an oauth token' do
13
+    before do
14
+      allow(controller).to receive(:doorkeeper_token) { token }
15
+    end
16
+
17
+    describe 'POST #create' do
18
+      let(:status) { Fabricate(:status, account: user.account) }
19
+
20
+      before do
21
+        post :create, params: { status_id: status.id }
22
+      end
23
+
24
+      it 'returns http success' do
25
+        expect(response).to have_http_status(:success)
26
+      end
27
+
28
+      it 'updates the favourites count' do
29
+        expect(status.favourites.count).to eq 1
30
+      end
31
+
32
+      it 'updates the favourited attribute' do
33
+        expect(user.account.favourited?(status)).to be true
34
+      end
35
+
36
+      it 'return json with updated attributes' do
37
+        hash_body = body_as_json
38
+
39
+        expect(hash_body[:id]).to eq status.id
40
+        expect(hash_body[:favourites_count]).to eq 1
41
+        expect(hash_body[:favourited]).to be true
42
+      end
43
+    end
44
+
45
+    describe 'POST #destroy' do
46
+      let(:status) { Fabricate(:status, account: user.account) }
47
+
48
+      before do
49
+        FavouriteService.new.call(user.account, status)
50
+        post :destroy, params: { status_id: status.id }
51
+      end
52
+
53
+      it 'returns http success' do
54
+        expect(response).to have_http_status(:success)
55
+      end
56
+
57
+      it 'updates the favourites count' do
58
+        expect(status.favourites.count).to eq 0
59
+      end
60
+
61
+      it 'updates the favourited attribute' do
62
+        expect(user.account.favourited?(status)).to be false
63
+      end
64
+    end
65
+  end
66
+end

+ 50
- 0
spec/controllers/api/v1/statuses/mutes_controller_spec.rb View File

@@ -0,0 +1,50 @@
1
+# frozen_string_literal: true
2
+
3
+require 'rails_helper'
4
+
5
+describe Api::V1::Statuses::MutesController do
6
+  render_views
7
+
8
+  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
9
+  let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
10
+  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
11
+
12
+  context 'with an oauth token' do
13
+    before do
14
+      allow(controller).to receive(:doorkeeper_token) { token }
15
+    end
16
+
17
+    describe 'POST #create' do
18
+      let(:status) { Fabricate(:status, account: user.account) }
19
+
20
+      before do
21
+        post :create, params: { status_id: status.id }
22
+      end
23
+
24
+      it 'returns http success' do
25
+        expect(response).to have_http_status(:success)
26
+      end
27
+
28
+      it 'creates a conversation mute' do
29
+        expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to_not be_nil
30
+      end
31
+    end
32
+
33
+    describe 'POST #destroy' do
34
+      let(:status) { Fabricate(:status, account: user.account) }
35
+
36
+      before do
37
+        user.account.mute_conversation!(status.conversation)
38
+        post :destroy, params: { status_id: status.id }
39
+      end
40
+
41
+      it 'returns http success' do
42
+        expect(response).to have_http_status(:success)
43
+      end
44
+
45
+      it 'destroys the conversation mute' do
46
+        expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil
47
+      end
48
+    end
49
+  end
50
+end

+ 66
- 0
spec/controllers/api/v1/statuses/reblogs_controller_spec.rb View File

@@ -0,0 +1,66 @@
1
+# frozen_string_literal: true
2
+
3
+require 'rails_helper'
4
+
5
+describe Api::V1::Statuses::ReblogsController do
6
+  render_views
7
+
8
+  let(:user)  { Fabricate(:user, account: Fabricate(:account, username: 'alice')) }
9
+  let(:app)   { Fabricate(:application, name: 'Test app', website: 'http://testapp.com') }
10
+  let(:token) { double acceptable?: true, resource_owner_id: user.id, application: app }
11
+
12
+  context 'with an oauth token' do
13
+    before do
14
+      allow(controller).to receive(:doorkeeper_token) { token }
15
+    end
16
+
17
+    describe 'POST #create' do
18
+      let(:status) { Fabricate(:status, account: user.account) }
19
+
20
+      before do
21
+        post :create, params: { status_id: status.id }
22
+      end
23
+
24
+      it 'returns http success' do
25
+        expect(response).to have_http_status(:success)
26
+      end
27
+
28
+      it 'updates the reblogs count' do
29
+        expect(status.reblogs.count).to eq 1
30
+      end
31
+
32
+      it 'updates the reblogged attribute' do
33
+        expect(user.account.reblogged?(status)).to be true
34
+      end
35
+
36
+      it 'return json with updated attributes' do
37
+        hash_body = body_as_json
38
+
39
+        expect(hash_body[:reblog][:id]).to eq status.id
40
+        expect(hash_body[:reblog][:reblogs_count]).to eq 1
41
+        expect(hash_body[:reblog][:reblogged]).to be true
42
+      end
43
+    end
44
+
45
+    describe 'POST #destroy' do
46
+      let(:status) { Fabricate(:status, account: user.account) }
47
+
48
+      before do
49
+        ReblogService.new.call(user.account, status)
50
+        post :destroy, params: { status_id: status.id }
51
+      end
52
+
53
+      it 'returns http success' do
54
+        expect(response).to have_http_status(:success)
55
+      end
56
+
57
+      it 'updates the reblogs count' do
58
+        expect(status.reblogs.count).to eq 0
59
+      end
60
+
61
+      it 'updates the reblogged attribute' do
62
+        expect(user.account.reblogged?(status)).to be false
63
+      end
64
+    end
65
+  end
66
+end

+ 0
- 131
spec/controllers/api/v1/statuses_controller_spec.rb View File

@@ -59,137 +59,6 @@ RSpec.describe Api::V1::StatusesController, type: :controller do
59 59
         expect(Status.find_by(id: status.id)).to be nil
60 60
       end
61 61
     end
62
-
63
-    describe 'POST #reblog' do
64
-      let(:status) { Fabricate(:status, account: user.account) }
65
-
66
-      before do
67
-        post :reblog, params: { id: status.id }
68
-      end
69
-
70
-      it 'returns http success' do
71
-        expect(response).to have_http_status(:success)
72
-      end
73
-
74
-      it 'updates the reblogs count' do
75
-        expect(status.reblogs.count).to eq 1
76
-      end
77
-
78
-      it 'updates the reblogged attribute' do
79
-        expect(user.account.reblogged?(status)).to be true
80
-      end
81
-
82
-      it 'return json with updated attributes' do
83
-        hash_body = body_as_json
84
-
85
-        expect(hash_body[:reblog][:id]).to eq status.id
86
-        expect(hash_body[:reblog][:reblogs_count]).to eq 1
87
-        expect(hash_body[:reblog][:reblogged]).to be true
88
-      end
89
-    end
90
-
91
-    describe 'POST #unreblog' do
92
-      let(:status) { Fabricate(:status, account: user.account) }
93
-
94
-      before do
95
-        post :reblog,   params: { id: status.id }
96
-        post :unreblog, params: { id: status.id }
97
-      end
98
-
99
-      it 'returns http success' do
100
-        expect(response).to have_http_status(:success)
101
-      end
102
-
103
-      it 'updates the reblogs count' do
104
-        expect(status.reblogs.count).to eq 0
105
-      end
106
-
107
-      it 'updates the reblogged attribute' do
108
-        expect(user.account.reblogged?(status)).to be false
109
-      end
110
-    end
111
-
112
-    describe 'POST #favourite' do
113
-      let(:status) { Fabricate(:status, account: user.account) }
114
-
115
-      before do
116
-        post :favourite, params: { id: status.id }
117
-      end
118
-
119
-      it 'returns http success' do
120
-        expect(response).to have_http_status(:success)
121
-      end
122
-
123
-      it 'updates the favourites count' do
124
-        expect(status.favourites.count).to eq 1
125
-      end
126
-
127
-      it 'updates the favourited attribute' do
128
-        expect(user.account.favourited?(status)).to be true
129
-      end
130
-
131
-      it 'return json with updated attributes' do
132
-        hash_body = body_as_json
133
-
134
-        expect(hash_body[:id]).to eq status.id
135
-        expect(hash_body[:favourites_count]).to eq 1
136
-        expect(hash_body[:favourited]).to be true
137
-      end
138
-    end
139
-
140
-    describe 'POST #unfavourite' do
141
-      let(:status) { Fabricate(:status, account: user.account) }
142
-
143
-      before do
144
-        post :favourite,   params: { id: status.id }
145
-        post :unfavourite, params: { id: status.id }
146
-      end
147
-
148
-      it 'returns http success' do
149
-        expect(response).to have_http_status(:success)
150
-      end
151
-
152
-      it 'updates the favourites count' do
153
-        expect(status.favourites.count).to eq 0
154
-      end
155
-
156
-      it 'updates the favourited attribute' do
157
-        expect(user.account.favourited?(status)).to be false
158
-      end
159
-    end
160
-
161
-    describe 'POST #mute' do
162
-      let(:status) { Fabricate(:status, account: user.account) }
163
-
164
-      before do
165
-        post :mute, params: { id: status.id }
166
-      end
167
-
168
-      it 'returns http success' do
169
-        expect(response).to have_http_status(:success)
170
-      end
171
-
172
-      it 'creates a conversation mute' do
173
-        expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to_not be_nil
174
-      end
175
-    end
176
-
177
-    describe 'POST #unmute' do
178
-      let(:status) { Fabricate(:status, account: user.account) }
179
-
180
-      before do
181
-        post :mute,   params: { id: status.id }
182
-        post :unmute, params: { id: status.id }
183
-      end
184
-
185
-      it 'returns http success' do
186
-        expect(response).to have_http_status(:success)
187
-      end
188
-
189
-      it 'destroys the conversation mute' do
190
-        expect(ConversationMute.find_by(account: user.account, conversation_id: status.conversation_id)).to be_nil
191
-      end
192
-    end
193 62
   end
194 63
 
195 64
   context 'without an oauth token' do

+ 32
- 0
spec/routing/api_routing_spec.rb View File

@@ -1,3 +1,5 @@
1
+# frozen_string_literal: true
2
+
1 3
 require 'rails_helper'
2 4
 
3 5
 describe 'API routes' do
@@ -50,6 +52,36 @@ describe 'API routes' do
50 52
       expect(get('/api/v1/statuses/123/favourited_by')).
51 53
         to route_to('api/v1/statuses/favourited_by_accounts#index', status_id: '123')
52 54
     end
55
+
56
+    it 'routes reblog' do
57
+      expect(post('/api/v1/statuses/123/reblog')).
58
+        to route_to('api/v1/statuses/reblogs#create', status_id: '123')
59
+    end
60
+
61
+    it 'routes unreblog' do
62
+      expect(post('/api/v1/statuses/123/unreblog')).
63
+        to route_to('api/v1/statuses/reblogs#destroy', status_id: '123')
64
+    end
65
+
66
+    it 'routes favourite' do
67
+      expect(post('/api/v1/statuses/123/favourite')).
68
+        to route_to('api/v1/statuses/favourites#create', status_id: '123')
69
+    end
70
+
71
+    it 'routes unfavourite' do
72
+      expect(post('/api/v1/statuses/123/unfavourite')).
73
+        to route_to('api/v1/statuses/favourites#destroy', status_id: '123')
74
+    end
75
+
76
+    it 'routes mute' do
77
+      expect(post('/api/v1/statuses/123/mute')).
78
+        to route_to('api/v1/statuses/mutes#create', status_id: '123')
79
+    end
80
+
81
+    it 'routes unmute' do
82
+      expect(post('/api/v1/statuses/123/unmute')).
83
+        to route_to('api/v1/statuses/mutes#destroy', status_id: '123')
84
+    end
53 85
   end
54 86
 
55 87
   describe 'Timeline routes' do

Loading…
Cancel
Save