From 2bb27e8dc640faac65e492051897b1d03940f3d8 Mon Sep 17 00:00:00 2001 From: Berkeley Martinez Date: Fri, 12 Jan 2018 11:09:09 -0800 Subject: [PATCH] feat(Flash): Get flash messages on load --- common/app/Flash/Flash.jsx | 2 +- common/app/Flash/redux/get-messages-epic.js | 17 ++++++++ common/app/Flash/redux/index.js | 46 ++++++--------------- common/app/Flash/redux/utils.js | 29 +++++++++++++ common/app/epics.js | 12 +++--- common/models/user.js | 10 +---- server/middlewares/sessions.js | 5 ++- 7 files changed, 73 insertions(+), 48 deletions(-) create mode 100644 common/app/Flash/redux/get-messages-epic.js create mode 100644 common/app/Flash/redux/utils.js diff --git a/common/app/Flash/Flash.jsx b/common/app/Flash/Flash.jsx index 59f8d427ee..52a549b0c7 100644 --- a/common/app/Flash/Flash.jsx +++ b/common/app/Flash/Flash.jsx @@ -4,8 +4,8 @@ import { CloseButton } from 'react-bootstrap'; import { connect } from 'react-redux'; import ns from './ns.json'; +import { alertTypes } from './redux/utils.js'; import { - alertTypes, latestMessageSelector, clickOnClose } from './redux'; diff --git a/common/app/Flash/redux/get-messages-epic.js b/common/app/Flash/redux/get-messages-epic.js new file mode 100644 index 0000000000..9e468a8abc --- /dev/null +++ b/common/app/Flash/redux/get-messages-epic.js @@ -0,0 +1,17 @@ +import { Observable } from 'rx'; +import { ofType } from 'redux-epic'; + +import { + fetchMessagesComplete, + fetchMessagesError +} from './'; +import { types as app } from '../../redux'; +import { getJSON$ } from '../../../utils/ajax-stream.js'; + +export default function getMessagesEpic(actions) { + return actions::ofType(app.appMounted) + .flatMap(() => getJSON$('/api/users/get-messages') + .map(fetchMessagesComplete) + .catch(err => Observable.of(fetchMessagesError(err))) + ); +} diff --git a/common/app/Flash/redux/index.js b/common/app/Flash/redux/index.js index 387e5de98f..f6df147c05 100644 --- a/common/app/Flash/redux/index.js +++ b/common/app/Flash/redux/index.js @@ -2,45 +2,25 @@ import _ from 'lodash/fp'; import { createTypes, createAction, + createAsyncTypes, composeReducers, handleActions } from 'berkeleys-redux-utils'; +import * as utils from './utils.js'; +import getMessagesEpic from './get-messages-epic.js'; import ns from '../ns.json'; -export const alertTypes = _.keyBy(_.identity)([ - 'success', - 'info', - 'warning', - 'danger' -]); -export const normalizeAlertType = alertType => alertTypes[alertType] || 'info'; - -export const getFlashAction = _.flow( - _.property('meta'), - _.property('flash') -); - -export const isFlashAction = _.flow( - getFlashAction, - Boolean -); - +export const epics = [getMessagesEpic]; export const types = createTypes([ 'clickOnClose', - 'messagesFoundOnBoot' + 'messagesFoundOnBoot', + createAsyncTypes('fetchMessages') ], ns); export const clickOnClose = createAction(types.clickOnClose, _.noop); -export const messagesFoundOnBoot = createAction(types.messagesFoundOnBoot); - -export const expressToStack = _.flow( - _.toPairs, - _.flatMap(([ type, messages ]) => messages.map(({ msg }) => ({ - message: msg, - alertType: normalizeAlertType(type) - }))) -); +export const fetchMessagesComplete = createAction(types.fetchMessages.complete); +export const fetchMessagesError = createAction(types.fetchMessages.error); const defaultState = []; @@ -57,20 +37,20 @@ export default composeReducers( handleActions( () => ({ [types.clickOnClose]: _.tail, - [types.messagesFoundOnBoot]: (state, { payload }) => [ + [types.fetchMessages.complete]: (state, { payload }) => [ ...state, - ...expressToStack(payload) + ...utils.expressToStack(payload) ] }), defaultState, ), function metaReducer(state = defaultState, action) { - if (isFlashAction(action)) { - const { payload: { alertType, message } } = getFlashAction(action); + if (utils.isFlashAction(action)) { + const { payload: { alertType, message } } = utils.getFlashAction(action); return [ ...state, { - alertType: normalizeAlertType(alertType), + alertType: utils.normalizeAlertType(alertType), message: _.escape(message) } ]; diff --git a/common/app/Flash/redux/utils.js b/common/app/Flash/redux/utils.js new file mode 100644 index 0000000000..e4ff2bacd3 --- /dev/null +++ b/common/app/Flash/redux/utils.js @@ -0,0 +1,29 @@ +import _ from 'lodash/fp'; + +export const alertTypes = _.keyBy(_.identity)([ + 'success', + 'info', + 'warning', + 'danger' +]); + +export const normalizeAlertType = alertType => alertTypes[alertType] || 'info'; + +export const getFlashAction = _.flow( + _.property('meta'), + _.property('flash') +); + +export const isFlashAction = _.flow( + getFlashAction, + Boolean +); + +export const expressToStack = _.flow( + _.toPairs, + _.flatMap(([ type, messages ]) => messages.map(({ msg }) => ({ + message: msg, + alertType: normalizeAlertType(type) + }))) +); + diff --git a/common/app/epics.js b/common/app/epics.js index 5c5372ed28..2ca7984b2d 100644 --- a/common/app/epics.js +++ b/common/app/epics.js @@ -1,15 +1,17 @@ import { epics as app } from './redux'; import { epics as challenge } from './routes/Challenges/redux'; -import { epics as settings } from './routes/Settings/redux'; -import { epics as nav } from './Nav/redux'; +import { epics as flash } from './Flash/redux'; import { epics as map } from './Map/redux'; +import { epics as nav } from './Nav/redux'; import { epics as panes } from './Panes/redux'; +import { epics as settings } from './routes/Settings/redux'; export default [ ...app, ...challenge, - ...settings, - ...nav, + ...flash, ...map, - ...panes + ...nav, + ...panes, + ...settings ]; diff --git a/common/models/user.js b/common/models/user.js index 6565274444..2dc6408f20 100644 --- a/common/models/user.js +++ b/common/models/user.js @@ -776,9 +776,7 @@ module.exports = function(User) { }); }; - User.getMessages = function getMessages(messages) { - return Promise.resolve(messages); - }; + User.getMessages = messages => Promise.resolve(messages); User.remoteMethod('getMessages', { http: { @@ -788,11 +786,7 @@ module.exports = function(User) { accepts: { arg: 'messages', type: 'object', - http: ctx => { - const messages = ctx.req.flash(); - console.log('messages: ', messages); - return messages; - } + http: ctx => ctx.req.flash() }, returns: [ { diff --git a/server/middlewares/sessions.js b/server/middlewares/sessions.js index 313f06d608..cc488cd790 100644 --- a/server/middlewares/sessions.js +++ b/server/middlewares/sessions.js @@ -9,7 +9,10 @@ export default function sessionsMiddleware() { return session({ // 900 day session cookie cookie: { maxAge: 900 * 24 * 60 * 60 * 1000 }, - resave: true, + // resave forces session to be resaved + // regardless of whether it was modified + // this causes race conditions during parallel req + resave: false, saveUninitialized: true, secret: sessionSecret, store: new MongoStore({ url })