Browse Source

Fix notifications in UI, added new API for fetching account relationships

Eugen Rochko 3 years ago
parent
commit
e46abc71ca

+ 7
- 0
app/assets/javascripts/components/actions/notifications.jsx View File

@@ -1,4 +1,5 @@
1 1
 export const NOTIFICATION_DISMISS = 'NOTIFICATION_DISMISS';
2
+export const NOTIFICATION_CLEAR   = 'NOTIFICATION_CLEAR';
2 3
 
3 4
 export function dismissNotification(notification) {
4 5
   return {
@@ -6,3 +7,9 @@ export function dismissNotification(notification) {
6 7
     notification: notification
7 8
   };
8 9
 };
10
+
11
+export function clearNotifications() {
12
+  return {
13
+    type: NOTIFICATION_CLEAR
14
+  };
15
+};

+ 12
- 12
app/assets/javascripts/components/features/ui/containers/notifications_container.jsx View File

@@ -1,18 +1,18 @@
1 1
 import { connect }             from 'react-redux';
2 2
 import { NotificationStack }   from 'react-notification';
3
-import { dismissNotification } from '../../../actions/notifications';
3
+import {
4
+  dismissNotification,
5
+  clearNotifications
6
+}                              from '../../../actions/notifications';
4 7
 
5
-const mapStateToProps = (state, props) => {
6
-  return {
7
-    notifications: state.get('notifications').map((item, i) => ({
8
-      message: item.get('message'),
9
-      title: item.get('title'),
10
-      key: i,
11
-      action: 'Dismiss',
12
-      dismissAfter: 5000
13
-    })).toJS()
14
-  };
15
-};
8
+const mapStateToProps = (state, props) => ({
9
+  notifications: state.get('notifications').map((item, i) => ({
10
+    message: item.get('message'),
11
+    title: item.get('title'),
12
+    key: item.get('key'),
13
+    dismissAfter: 5000
14
+  })).toJS()
15
+});
16 16
 
17 17
 const mapDispatchToProps = (dispatch) => {
18 18
   return {

+ 4
- 1
app/assets/javascripts/components/reducers/notifications.jsx View File

@@ -2,13 +2,14 @@ import { COMPOSE_SUBMIT_FAIL, COMPOSE_UPLOAD_FAIL } from '../actions/compose';
2 2
 import { FOLLOW_SUBMIT_FAIL }                       from '../actions/follow';
3 3
 import { REBLOG_FAIL, FAVOURITE_FAIL }              from '../actions/interactions';
4 4
 import { TIMELINE_REFRESH_FAIL }                    from '../actions/timelines';
5
-import { NOTIFICATION_DISMISS }                     from '../actions/notifications';
5
+import { NOTIFICATION_DISMISS, NOTIFICATION_CLEAR } from '../actions/notifications';
6 6
 import Immutable                                    from 'immutable';
7 7
 
8 8
 const initialState = Immutable.List();
9 9
 
10 10
 function notificationFromError(state, error) {
11 11
   let n = Immutable.Map({
12
+    key: state.size > 0 ? state.last().get('key') + 1 : 0,
12 13
     message: ''
13 14
   });
14 15
 
@@ -34,6 +35,8 @@ export default function notifications(state = initialState, action) {
34 35
     case TIMELINE_REFRESH_FAIL:
35 36
       return notificationFromError(state, action.error);
36 37
     case NOTIFICATION_DISMISS:
38
+      return state.filterNot(item => item.get('key') === action.notification.key);
39
+    case NOTIFICATION_CLEAR:
37 40
       return state.clear();
38 41
     default:
39 42
       return state;

+ 8
- 0
app/controllers/api/accounts_controller.rb View File

@@ -28,6 +28,14 @@ class Api::AccountsController < ApiController
28 28
     render action: :show
29 29
   end
30 30
 
31
+  def relationships
32
+    ids = params[:id].is_a?(Enumerable) ? params[:id].map { |id| id.to_i } : [params[:id].to_i]
33
+    @accounts    = Account.find(ids)
34
+    @following   = Account.following_map(ids, current_user.account_id)
35
+    @followed_by = Account.followed_by_map(ids, current_user.account_id)
36
+    @blocking    = {}
37
+  end
38
+
31 39
   private
32 40
 
33 41
   def set_account

+ 8
- 0
app/models/account.rb View File

@@ -127,6 +127,14 @@ class Account < ApplicationRecord
127 127
     nil
128 128
   end
129 129
 
130
+  def self.following_map(target_account_ids, account_id)
131
+    Follow.where(target_account_id: target_account_ids).where(account_id: account_id).map { |f| [f.target_account_id, true] }.to_h
132
+  end
133
+
134
+  def self.followed_by_map(target_account_ids, account_id)
135
+    Follow.where(account_id: target_account_ids).where(target_account_id: account_id).map { |f| [f.account_id, true] }.to_h
136
+  end
137
+
130 138
   before_create do
131 139
     if local?
132 140
       keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 1024 : 2048)

+ 0
- 2
app/views/api/accounts/lookup/index.rabl View File

@@ -1,2 +0,0 @@
1
-collection @accounts
2
-extends('api/accounts/show')

+ 5
- 0
app/views/api/accounts/relationships.rabl View File

@@ -0,0 +1,5 @@
1
+collection @accounts
2
+attribute :id
3
+node(:following)   { |account| @following[account.id]   || false }
4
+node(:followed_by) { |account| @followed_by[account.id] || false }
5
+node(:blocking)    { |account| @blocking[account.id]    || false }

+ 0
- 1
app/views/api/accounts/show.rabl View File

@@ -8,4 +8,3 @@ node(:header)          { |account| full_asset_url(account.header.url(:medium, fa
8 8
 node(:followers_count) { |account| account.followers.count }
9 9
 node(:following_count) { |account| account.following.count }
10 10
 node(:statuses_count)  { |account| account.statuses.count  }
11
-node(:following)       { |account| current_account.following?(account) }

+ 4
- 0
config/routes.rb View File

@@ -59,6 +59,10 @@ Rails.application.routes.draw do
59 59
     resources :media,    only: [:create]
60 60
 
61 61
     resources :accounts, only: [:show] do
62
+      collection do
63
+        get :relationships
64
+      end
65
+
62 66
       member do
63 67
         get :statuses
64 68
         get :followers

+ 42
- 0
spec/controllers/api/accounts_controller_spec.rb View File

@@ -71,4 +71,46 @@ RSpec.describe Api::AccountsController, type: :controller do
71 71
       expect(user.account.following?(other_account)).to be false
72 72
     end
73 73
   end
74
+
75
+  describe 'GET #relationships' do
76
+    let(:simon) { Fabricate(:user, email: 'simon@example.com', account: Fabricate(:account, username: 'simon')).account }
77
+    let(:lewis) { Fabricate(:user, email: 'lewis@example.com', account: Fabricate(:account, username: 'lewis')).account }
78
+
79
+    before do
80
+      user.account.follow!(simon)
81
+      lewis.follow!(user.account)
82
+    end
83
+
84
+    context 'provided only one ID' do
85
+      before do
86
+        get :relationships, params: { id: simon.id }
87
+      end
88
+
89
+      it 'returns http success' do
90
+        expect(response).to have_http_status(:success)
91
+      end
92
+
93
+      it 'returns JSON with correct data' do
94
+        json = body_as_json
95
+
96
+        expect(json).to be_a Enumerable
97
+        expect(json.first[:following]).to be true
98
+        expect(json.first[:followed_by]).to be false
99
+      end
100
+    end
101
+
102
+    context 'provided multiple IDs' do
103
+      before do
104
+        get :relationships, params: { id: [simon.id, lewis.id] }
105
+      end
106
+
107
+      it 'returns http success' do
108
+        expect(response).to have_http_status(:success)
109
+      end
110
+
111
+      xit 'returns JSON with correct data' do
112
+        # todo
113
+      end
114
+    end
115
+  end
74 116
 end

+ 1
- 1
spec/spec_helper.rb View File

@@ -23,5 +23,5 @@ def body_as_json
23 23
 end
24 24
 
25 25
 def json_str_to_hash(str)
26
-  JSON.parse(str).with_indifferent_access
26
+  JSON.parse(str, symbolize_names: true)
27 27
 end