Added verified accounts/suggestions panel, updated suggestions route
• Added: - verified accounts/suggestions panel • Updated: - suggestions route
This commit is contained in:
parent
095e646661
commit
f41274efc7
|
@ -5,12 +5,21 @@ class Api::V1::SuggestionsController < Api::BaseController
|
|||
|
||||
before_action -> { doorkeeper_authorize! :read }
|
||||
before_action :require_user!
|
||||
before_action :set_accounts
|
||||
|
||||
respond_to :json
|
||||
|
||||
def index
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
type = params[:type]
|
||||
|
||||
if type == 'related'
|
||||
@accounts = PotentialFriendshipTracker.get(current_account.id)
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
elsif type == 'verified'
|
||||
@accounts = VerifiedSuggestions.get(current_account.id)
|
||||
render json: @accounts, each_serializer: REST::AccountSerializer
|
||||
else
|
||||
raise GabSocial::NotPermittedError
|
||||
end
|
||||
end
|
||||
|
||||
def destroy
|
||||
|
@ -18,9 +27,4 @@ class Api::V1::SuggestionsController < Api::BaseController
|
|||
render_empty
|
||||
end
|
||||
|
||||
private
|
||||
|
||||
def set_accounts
|
||||
@accounts = PotentialFriendshipTracker.get(current_account.id)
|
||||
end
|
||||
end
|
||||
|
|
|
@ -1,57 +1,77 @@
|
|||
import api from '../api'
|
||||
import { importFetchedAccounts } from './importer'
|
||||
import { me } from '../initial_state'
|
||||
import {
|
||||
SUGGESTION_TYPE_VERIFIED,
|
||||
SUGGESTION_TYPE_RELATED,
|
||||
} from '../constants'
|
||||
|
||||
export const SUGGESTIONS_FETCH_REQUEST = 'SUGGESTIONS_FETCH_REQUEST'
|
||||
export const SUGGESTIONS_FETCH_SUCCESS = 'SUGGESTIONS_FETCH_SUCCESS'
|
||||
export const SUGGESTIONS_FETCH_FAIL = 'SUGGESTIONS_FETCH_FAIL'
|
||||
|
||||
export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS';
|
||||
export const SUGGESTIONS_DISMISS = 'SUGGESTIONS_DISMISS'
|
||||
|
||||
export function fetchSuggestions() {
|
||||
export function fetchPopularSuggestions() {
|
||||
return (dispatch, getState) => {
|
||||
if (!me) return false
|
||||
|
||||
dispatch(fetchSuggestionsRequest());
|
||||
dispatch(fetchSuggestionsRequest(SUGGESTION_TYPE_VERIFIED))
|
||||
|
||||
api(getState).get('/api/v1/suggestions').then(response => {
|
||||
dispatch(importFetchedAccounts(response.data));
|
||||
dispatch(fetchSuggestionsSuccess(response.data));
|
||||
}).catch(error => dispatch(fetchSuggestionsFail(error)));
|
||||
};
|
||||
};
|
||||
api(getState).get(`/api/v1/suggestions?type=${SUGGESTION_TYPE_VERIFIED}`).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data))
|
||||
dispatch(fetchSuggestionsSuccess(response.data, SUGGESTION_TYPE_VERIFIED))
|
||||
}).catch(error => dispatch(fetchSuggestionsFail(error, SUGGESTION_TYPE_VERIFIED)))
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchSuggestionsRequest() {
|
||||
export function fetchRelatedSuggestions() {
|
||||
return (dispatch, getState) => {
|
||||
if (!me) return false
|
||||
|
||||
dispatch(fetchSuggestionsRequest(SUGGESTION_TYPE_RELATED))
|
||||
|
||||
api(getState).get(`/api/v1/suggestions?type=${SUGGESTION_TYPE_RELATED}`).then(response => {
|
||||
dispatch(importFetchedAccounts(response.data))
|
||||
dispatch(fetchSuggestionsSuccess(response.data, SUGGESTION_TYPE_RELATED))
|
||||
}).catch(error => dispatch(fetchSuggestionsFail(error, SUGGESTION_TYPE_RELATED)))
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchSuggestionsRequest(suggestionType) {
|
||||
return {
|
||||
type: SUGGESTIONS_FETCH_REQUEST,
|
||||
skipLoading: true,
|
||||
};
|
||||
};
|
||||
suggestionType,
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchSuggestionsSuccess(accounts) {
|
||||
export function fetchSuggestionsSuccess(accounts, suggestionType) {
|
||||
return {
|
||||
type: SUGGESTIONS_FETCH_SUCCESS,
|
||||
accounts,
|
||||
skipLoading: true,
|
||||
};
|
||||
};
|
||||
accounts,
|
||||
suggestionType
|
||||
}
|
||||
}
|
||||
|
||||
export function fetchSuggestionsFail(error) {
|
||||
export function fetchSuggestionsFail(error, suggestionType) {
|
||||
return {
|
||||
type: SUGGESTIONS_FETCH_FAIL,
|
||||
error,
|
||||
skipLoading: true,
|
||||
skipAlert: true,
|
||||
};
|
||||
};
|
||||
error,
|
||||
suggestionType,
|
||||
}
|
||||
}
|
||||
|
||||
export const dismissSuggestion = accountId => (dispatch, getState) => {
|
||||
if (!me) return;
|
||||
export const dismissRelatedSuggestion = (accountId) => (dispatch, getState) => {
|
||||
if (!me) return
|
||||
|
||||
dispatch({
|
||||
type: SUGGESTIONS_DISMISS,
|
||||
id: accountId,
|
||||
});
|
||||
})
|
||||
|
||||
api(getState).delete(`/api/v1/suggestions/${accountId}`);
|
||||
};
|
||||
api(getState).delete(`/api/v1/suggestions/related/${accountId}`)
|
||||
}
|
|
@ -0,0 +1,90 @@
|
|||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { fetchPopularSuggestions } from '../../actions/suggestions'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import Account from '../account'
|
||||
import PanelLayout from './panel_layout'
|
||||
|
||||
const messages = defineMessages({
|
||||
dismissSuggestion: { id: 'suggestions.dismiss', defaultMessage: 'Dismiss suggestion' },
|
||||
title: { id: 'who_to_follow.title', defaultMessage: 'Verified Accounts to Follow' },
|
||||
show_more: { id: 'who_to_follow.more', defaultMessage: 'Show more' },
|
||||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
suggestions: state.getIn(['suggestions', 'verified', 'items']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
fetchPopularSuggestions: () => dispatch(fetchPopularSuggestions()),
|
||||
})
|
||||
|
||||
export default
|
||||
@connect(mapStateToProps, mapDispatchToProps)
|
||||
@injectIntl
|
||||
class VerifiedAccountsPanel extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
fetchPopularSuggestions: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
suggestions: ImmutablePropTypes.list.isRequired,
|
||||
isLazy: PropTypes.bool,
|
||||
}
|
||||
|
||||
state = {
|
||||
fetched: !this.props.isLazy,
|
||||
}
|
||||
|
||||
updateOnProps = [
|
||||
'suggestions',
|
||||
'isLazy',
|
||||
'shouldLoad',
|
||||
]
|
||||
|
||||
static getDerivedStateFromProps(nextProps, prevState) {
|
||||
if (nextProps.shouldLoad && !prevState.fetched) {
|
||||
return { fetched: true }
|
||||
}
|
||||
|
||||
return null
|
||||
}
|
||||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.fetched && this.state.fetched) {
|
||||
this.props.fetchPopularSuggestions()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.isLazy) {
|
||||
this.props.fetchPopularSuggestions()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, suggestions } = this.props
|
||||
|
||||
if (suggestions.isEmpty()) return null
|
||||
|
||||
return (
|
||||
<PanelLayout
|
||||
noPadding
|
||||
title={intl.formatMessage(messages.title)}
|
||||
// footerButtonTitle={intl.formatMessage(messages.show_more)}
|
||||
// footerButtonTo='/explore'
|
||||
>
|
||||
<div className={_s.default}>
|
||||
{
|
||||
suggestions.map(accountId => (
|
||||
<Account
|
||||
compact
|
||||
key={accountId}
|
||||
id={accountId}
|
||||
/>
|
||||
))
|
||||
}
|
||||
</div>
|
||||
</PanelLayout>
|
||||
)
|
||||
}
|
||||
}
|
|
@ -1,5 +1,8 @@
|
|||
import { defineMessages, injectIntl } from 'react-intl'
|
||||
import { fetchSuggestions, dismissSuggestion } from '../../actions/suggestions'
|
||||
import {
|
||||
fetchRelatedSuggestions,
|
||||
dismissRelatedSuggestion,
|
||||
} from '../../actions/suggestions'
|
||||
import ImmutablePureComponent from 'react-immutable-pure-component'
|
||||
import ImmutablePropTypes from 'react-immutable-proptypes'
|
||||
import Account from '../../components/account'
|
||||
|
@ -12,12 +15,12 @@ const messages = defineMessages({
|
|||
})
|
||||
|
||||
const mapStateToProps = (state) => ({
|
||||
suggestions: state.getIn(['suggestions', 'items']),
|
||||
suggestions: state.getIn(['suggestions', 'related', 'items']),
|
||||
})
|
||||
|
||||
const mapDispatchToProps = (dispatch) => ({
|
||||
fetchSuggestions: () => dispatch(fetchSuggestions()),
|
||||
dismissSuggestion: (account) => dispatch(dismissSuggestion(account.get('id'))),
|
||||
fetchRelatedSuggestions: () => dispatch(fetchRelatedSuggestions()),
|
||||
dismissRelatedSuggestion: (account) => dispatch(dismissRelatedSuggestion(account.get('id'))),
|
||||
})
|
||||
|
||||
export default
|
||||
|
@ -26,8 +29,8 @@ export default
|
|||
class WhoToFollowPanel extends ImmutablePureComponent {
|
||||
|
||||
static propTypes = {
|
||||
dismissSuggestion: PropTypes.func.isRequired,
|
||||
fetchSuggestions: PropTypes.func.isRequired,
|
||||
dismissRelatedSuggestion: PropTypes.func.isRequired,
|
||||
fetchRelatedSuggestions: PropTypes.func.isRequired,
|
||||
intl: PropTypes.object.isRequired,
|
||||
suggestions: ImmutablePropTypes.list.isRequired,
|
||||
isLazy: PropTypes.bool,
|
||||
|
@ -53,18 +56,22 @@ class WhoToFollowPanel extends ImmutablePureComponent {
|
|||
|
||||
componentDidUpdate(prevProps, prevState) {
|
||||
if (!prevState.fetched && this.state.fetched) {
|
||||
this.props.fetchSuggestions()
|
||||
this.props.fetchRelatedSuggestions()
|
||||
}
|
||||
}
|
||||
|
||||
componentDidMount() {
|
||||
if (!this.props.isLazy) {
|
||||
this.props.fetchSuggestions()
|
||||
this.props.fetchRelatedSuggestions()
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
const { intl, suggestions, dismissSuggestion } = this.props
|
||||
const {
|
||||
intl,
|
||||
suggestions,
|
||||
dismissRelatedSuggestion,
|
||||
} = this.props
|
||||
|
||||
if (suggestions.isEmpty()) return null
|
||||
|
||||
|
@ -72,8 +79,8 @@ class WhoToFollowPanel extends ImmutablePureComponent {
|
|||
<PanelLayout
|
||||
noPadding
|
||||
title={intl.formatMessage(messages.title)}
|
||||
// footerButtonTitle={intl.formatMessage(messages.show_more)}
|
||||
// footerButtonTo='/explore'
|
||||
footerButtonTitle={intl.formatMessage(messages.show_more)}
|
||||
footerButtonTo='/explore'
|
||||
>
|
||||
<div className={_s.default}>
|
||||
{
|
||||
|
@ -83,7 +90,7 @@ class WhoToFollowPanel extends ImmutablePureComponent {
|
|||
showDismiss
|
||||
key={accountId}
|
||||
id={accountId}
|
||||
dismissAction={dismissSuggestion}
|
||||
dismissAction={dismissRelatedSuggestion}
|
||||
/>
|
||||
))
|
||||
}
|
||||
|
|
|
@ -3,28 +3,38 @@ import {
|
|||
SUGGESTIONS_FETCH_SUCCESS,
|
||||
SUGGESTIONS_FETCH_FAIL,
|
||||
SUGGESTIONS_DISMISS,
|
||||
} from '../actions/suggestions';
|
||||
import { Map as ImmutableMap, List as ImmutableList, fromJS } from 'immutable';
|
||||
} from '../actions/suggestions'
|
||||
import {
|
||||
Map as ImmutableMap,
|
||||
List as ImmutableList,
|
||||
fromJS,
|
||||
} from 'immutable'
|
||||
|
||||
const initialState = ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
});
|
||||
related: ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
}),
|
||||
verified: ImmutableMap({
|
||||
items: ImmutableList(),
|
||||
isLoading: false,
|
||||
}),
|
||||
})
|
||||
|
||||
export default function suggestionsReducer(state = initialState, action) {
|
||||
switch(action.type) {
|
||||
case SUGGESTIONS_FETCH_REQUEST:
|
||||
return state.set('isLoading', true);
|
||||
return state.setIn([action.suggestionType, 'isLoading'], true)
|
||||
case SUGGESTIONS_FETCH_SUCCESS:
|
||||
return state.withMutations(map => {
|
||||
map.set('items', fromJS(action.accounts.map(x => x.id)));
|
||||
map.set('isLoading', false);
|
||||
});
|
||||
return state.withMutations((map) => {
|
||||
map.setIn([action.suggestionType, 'items'], fromJS(action.accounts.map(x => x.id)))
|
||||
map.setIn([action.suggestionType, 'isLoading'], false)
|
||||
})
|
||||
case SUGGESTIONS_FETCH_FAIL:
|
||||
return state.set('isLoading', false);
|
||||
return state.setIn([action.suggestionType, 'isLoading'], false)
|
||||
case SUGGESTIONS_DISMISS:
|
||||
return state.update('items', list => list.filterNot(id => id === action.id));
|
||||
return state.updateIn([action.suggestionType, 'items'], list => list.filterNot(id => id === action.id))
|
||||
default:
|
||||
return state;
|
||||
return state
|
||||
}
|
||||
};
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
# frozen_string_literal: true
|
||||
|
||||
class VerifiedSuggestions
|
||||
EXPIRE_AFTER = 12.minute.seconds
|
||||
MAX_ITEMS = 12
|
||||
KEY = 'popularsuggestions'
|
||||
|
||||
class << self
|
||||
include Redisable
|
||||
|
||||
def set(account_ids)
|
||||
return if account_ids.nil? || account_ids.empty?
|
||||
redis.setex(KEY, EXPIRE_AFTER, account_ids)
|
||||
end
|
||||
|
||||
def get(account_id)
|
||||
account_ids = redis.get(KEY)
|
||||
|
||||
if account_ids.nil? || account_ids.empty?
|
||||
account_ids = Account.searchable
|
||||
.where(is_verified: true)
|
||||
.discoverable
|
||||
.by_recent_status
|
||||
.local
|
||||
.limit(MAX_ITEMS)
|
||||
.pluck(:id)
|
||||
|
||||
set(account_ids) if account_ids.nil? || account_ids.empty?
|
||||
else
|
||||
account_ids = JSON.parse(account_ids)
|
||||
end
|
||||
|
||||
return [] if account_ids.nil? || account_ids.empty?
|
||||
|
||||
Account.where(id: account_ids)
|
||||
end
|
||||
end
|
||||
end
|
Loading…
Reference in New Issue