From e46abc71cad590c2113247d41feca80d27a557b0 Mon Sep 17 00:00:00 2001 From: Eugen Rochko Date: Wed, 21 Sep 2016 22:07:18 +0200 Subject: [PATCH] Fix notifications in UI, added new API for fetching account relationships --- .../components/actions/notifications.jsx | 7 ++++ .../ui/containers/notifications_container.jsx | 24 +++++------ .../components/reducers/notifications.jsx | 5 ++- app/controllers/api/accounts_controller.rb | 8 ++++ app/models/account.rb | 8 ++++ app/views/api/accounts/lookup/index.rabl | 2 - app/views/api/accounts/relationships.rabl | 5 +++ app/views/api/accounts/show.rabl | 1 - config/routes.rb | 4 ++ .../api/accounts_controller_spec.rb | 42 +++++++++++++++++++ spec/spec_helper.rb | 2 +- 11 files changed, 91 insertions(+), 17 deletions(-) delete mode 100644 app/views/api/accounts/lookup/index.rabl create mode 100644 app/views/api/accounts/relationships.rabl diff --git a/app/assets/javascripts/components/actions/notifications.jsx b/app/assets/javascripts/components/actions/notifications.jsx index cf1e50a69c..f19a356c26 100644 --- a/app/assets/javascripts/components/actions/notifications.jsx +++ b/app/assets/javascripts/components/actions/notifications.jsx @@ -1,4 +1,5 @@ export const NOTIFICATION_DISMISS = 'NOTIFICATION_DISMISS'; +export const NOTIFICATION_CLEAR = 'NOTIFICATION_CLEAR'; export function dismissNotification(notification) { return { @@ -6,3 +7,9 @@ export function dismissNotification(notification) { notification: notification }; }; + +export function clearNotifications() { + return { + type: NOTIFICATION_CLEAR + }; +}; diff --git a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx index 2db1603fc8..bc339ef282 100644 --- a/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx +++ b/app/assets/javascripts/components/features/ui/containers/notifications_container.jsx @@ -1,18 +1,18 @@ import { connect } from 'react-redux'; import { NotificationStack } from 'react-notification'; -import { dismissNotification } from '../../../actions/notifications'; +import { + dismissNotification, + clearNotifications +} from '../../../actions/notifications'; -const mapStateToProps = (state, props) => { - return { - notifications: state.get('notifications').map((item, i) => ({ - message: item.get('message'), - title: item.get('title'), - key: i, - action: 'Dismiss', - dismissAfter: 5000 - })).toJS() - }; -}; +const mapStateToProps = (state, props) => ({ + notifications: state.get('notifications').map((item, i) => ({ + message: item.get('message'), + title: item.get('title'), + key: item.get('key'), + dismissAfter: 5000 + })).toJS() +}); const mapDispatchToProps = (dispatch) => { return { diff --git a/app/assets/javascripts/components/reducers/notifications.jsx b/app/assets/javascripts/components/reducers/notifications.jsx index 694d9ff4b2..a1d99f0e10 100644 --- a/app/assets/javascripts/components/reducers/notifications.jsx +++ b/app/assets/javascripts/components/reducers/notifications.jsx @@ -2,13 +2,14 @@ import { COMPOSE_SUBMIT_FAIL, COMPOSE_UPLOAD_FAIL } from '../actions/compose'; import { FOLLOW_SUBMIT_FAIL } from '../actions/follow'; import { REBLOG_FAIL, FAVOURITE_FAIL } from '../actions/interactions'; import { TIMELINE_REFRESH_FAIL } from '../actions/timelines'; -import { NOTIFICATION_DISMISS } from '../actions/notifications'; +import { NOTIFICATION_DISMISS, NOTIFICATION_CLEAR } from '../actions/notifications'; import Immutable from 'immutable'; const initialState = Immutable.List(); function notificationFromError(state, error) { let n = Immutable.Map({ + key: state.size > 0 ? state.last().get('key') + 1 : 0, message: '' }); @@ -34,6 +35,8 @@ export default function notifications(state = initialState, action) { case TIMELINE_REFRESH_FAIL: return notificationFromError(state, action.error); case NOTIFICATION_DISMISS: + return state.filterNot(item => item.get('key') === action.notification.key); + case NOTIFICATION_CLEAR: return state.clear(); default: return state; diff --git a/app/controllers/api/accounts_controller.rb b/app/controllers/api/accounts_controller.rb index 1a19ae43eb..ccbbc93fff 100644 --- a/app/controllers/api/accounts_controller.rb +++ b/app/controllers/api/accounts_controller.rb @@ -28,6 +28,14 @@ class Api::AccountsController < ApiController render action: :show end + def relationships + ids = params[:id].is_a?(Enumerable) ? params[:id].map { |id| id.to_i } : [params[:id].to_i] + @accounts = Account.find(ids) + @following = Account.following_map(ids, current_user.account_id) + @followed_by = Account.followed_by_map(ids, current_user.account_id) + @blocking = {} + end + private def set_account diff --git a/app/models/account.rb b/app/models/account.rb index bfb10ae516..25316c9a43 100644 --- a/app/models/account.rb +++ b/app/models/account.rb @@ -127,6 +127,14 @@ class Account < ApplicationRecord nil end + def self.following_map(target_account_ids, account_id) + Follow.where(target_account_id: target_account_ids).where(account_id: account_id).map { |f| [f.target_account_id, true] }.to_h + end + + def self.followed_by_map(target_account_ids, account_id) + Follow.where(account_id: target_account_ids).where(target_account_id: account_id).map { |f| [f.account_id, true] }.to_h + end + before_create do if local? keypair = OpenSSL::PKey::RSA.new(Rails.env.test? ? 1024 : 2048) diff --git a/app/views/api/accounts/lookup/index.rabl b/app/views/api/accounts/lookup/index.rabl deleted file mode 100644 index f6ae172edb..0000000000 --- a/app/views/api/accounts/lookup/index.rabl +++ /dev/null @@ -1,2 +0,0 @@ -collection @accounts -extends('api/accounts/show') diff --git a/app/views/api/accounts/relationships.rabl b/app/views/api/accounts/relationships.rabl new file mode 100644 index 0000000000..dc1c1bc65d --- /dev/null +++ b/app/views/api/accounts/relationships.rabl @@ -0,0 +1,5 @@ +collection @accounts +attribute :id +node(:following) { |account| @following[account.id] || false } +node(:followed_by) { |account| @followed_by[account.id] || false } +node(:blocking) { |account| @blocking[account.id] || false } diff --git a/app/views/api/accounts/show.rabl b/app/views/api/accounts/show.rabl index 5af295c54b..4f6a3ff998 100644 --- a/app/views/api/accounts/show.rabl +++ b/app/views/api/accounts/show.rabl @@ -8,4 +8,3 @@ node(:header) { |account| full_asset_url(account.header.url(:medium, fa node(:followers_count) { |account| account.followers.count } node(:following_count) { |account| account.following.count } node(:statuses_count) { |account| account.statuses.count } -node(:following) { |account| current_account.following?(account) } diff --git a/config/routes.rb b/config/routes.rb index 1232f8a442..51f1f86da0 100644 --- a/config/routes.rb +++ b/config/routes.rb @@ -59,6 +59,10 @@ Rails.application.routes.draw do resources :media, only: [:create] resources :accounts, only: [:show] do + collection do + get :relationships + end + member do get :statuses get :followers diff --git a/spec/controllers/api/accounts_controller_spec.rb b/spec/controllers/api/accounts_controller_spec.rb index 91e62837cf..7fef8b9fe4 100644 --- a/spec/controllers/api/accounts_controller_spec.rb +++ b/spec/controllers/api/accounts_controller_spec.rb @@ -71,4 +71,46 @@ RSpec.describe Api::AccountsController, type: :controller do expect(user.account.following?(other_account)).to be false end end + + describe 'GET #relationships' do + let(:simon) { Fabricate(:user, email: 'simon@example.com', account: Fabricate(:account, username: 'simon')).account } + let(:lewis) { Fabricate(:user, email: 'lewis@example.com', account: Fabricate(:account, username: 'lewis')).account } + + before do + user.account.follow!(simon) + lewis.follow!(user.account) + end + + context 'provided only one ID' do + before do + get :relationships, params: { id: simon.id } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + it 'returns JSON with correct data' do + json = body_as_json + + expect(json).to be_a Enumerable + expect(json.first[:following]).to be true + expect(json.first[:followed_by]).to be false + end + end + + context 'provided multiple IDs' do + before do + get :relationships, params: { id: [simon.id, lewis.id] } + end + + it 'returns http success' do + expect(response).to have_http_status(:success) + end + + xit 'returns JSON with correct data' do + # todo + end + end + end end diff --git a/spec/spec_helper.rb b/spec/spec_helper.rb index 8d77b39f32..2fdce87550 100644 --- a/spec/spec_helper.rb +++ b/spec/spec_helper.rb @@ -23,5 +23,5 @@ def body_as_json end def json_str_to_hash(str) - JSON.parse(str).with_indifferent_access + JSON.parse(str, symbolize_names: true) end