fix(user/settings): Add theme server validations

This commit is contained in:
Berkeley Martinez
2018-01-29 11:26:24 -08:00
parent b8f8ea80cf
commit ae3ccdd672
6 changed files with 47 additions and 34 deletions

View File

@ -2,7 +2,6 @@ import analyticsEpic from './analytics-epic.js';
import errEpic from './err-epic.js'; import errEpic from './err-epic.js';
import hardGoToEpic from './hard-go-to-epic.js'; import hardGoToEpic from './hard-go-to-epic.js';
import mouseTrapEpic from './mouse-trap-epic.js'; import mouseTrapEpic from './mouse-trap-epic.js';
import nightModeEpic from './night-mode-epic.js';
import titleEpic from './title-epic.js'; import titleEpic from './title-epic.js';
export default [ export default [
@ -10,6 +9,5 @@ export default [
errEpic, errEpic,
hardGoToEpic, hardGoToEpic,
mouseTrapEpic, mouseTrapEpic,
nightModeEpic,
titleEpic titleEpic
]; ];

View File

@ -12,9 +12,11 @@ import { createSelector } from 'reselect';
import fetchUserEpic from './fetch-user-epic.js'; import fetchUserEpic from './fetch-user-epic.js';
import updateMyCurrentChallengeEpic from './update-my-challenge-epic.js'; import updateMyCurrentChallengeEpic from './update-my-challenge-epic.js';
import fetchChallengesEpic from './fetch-challenges-epic.js'; import fetchChallengesEpic from './fetch-challenges-epic.js';
import nightModeEpic from './night-mode-epic.js';
import { createFilesMetaCreator } from '../files'; import { createFilesMetaCreator } from '../files';
import { updateThemeMetacreator, entitiesSelector } from '../entities'; import { updateThemeMetacreator, entitiesSelector } from '../entities';
import { utils } from '../Flash/redux';
import { types as challenges } from '../routes/Challenges/redux'; import { types as challenges } from '../routes/Challenges/redux';
import { challengeToFiles } from '../routes/Challenges/utils'; import { challengeToFiles } from '../routes/Challenges/utils';
@ -23,8 +25,9 @@ import ns from '../ns.json';
import { themes, invertTheme } from '../../utils/themes.js'; import { themes, invertTheme } from '../../utils/themes.js';
export const epics = [ export const epics = [
fetchUserEpic,
fetchChallengesEpic, fetchChallengesEpic,
fetchUserEpic,
nightModeEpic,
updateMyCurrentChallengeEpic updateMyCurrentChallengeEpic
]; ];
@ -48,7 +51,7 @@ export const types = createTypes([
// night mode // night mode
'toggleNightMode', 'toggleNightMode',
'postThemeComplete' createAsyncTypes('postTheme')
], ns); ], ns);
const throwIfUndefined = () => { const throwIfUndefined = () => {
@ -130,6 +133,7 @@ export const createErrorObservable = error => Observable.just({
type: types.handleError, type: types.handleError,
error error
}); });
// use sparingly
// doActionOnError( // doActionOnError(
// actionCreator: (() => Action|Null) // actionCreator: (() => Action|Null)
// ) => (error: Error) => Observable[Action] // ) => (error: Error) => Observable[Action]
@ -147,9 +151,18 @@ export const toggleNightMode = createAction(
(username, theme) => updateThemeMetacreator(username, invertTheme(theme)) (username, theme) => updateThemeMetacreator(username, invertTheme(theme))
); );
export const postThemeComplete = createAction( export const postThemeComplete = createAction(
types.postThemeComplete, types.postTheme.complete,
null, null,
updateThemeMetacreator utils.createFlashMetaAction
);
export const postThemeError = createAction(
types.postTheme.error,
null,
(username, theme, err) => ({
...updateThemeMetacreator(username, invertTheme(theme)),
...utils.createFlashMetaAction(err)
})
); );
const defaultState = { const defaultState = {

View File

@ -6,14 +6,12 @@ import store from 'store';
import { themes } from '../../utils/themes.js'; import { themes } from '../../utils/themes.js';
import { postJSON$ } from '../../utils/ajax-stream.js'; import { postJSON$ } from '../../utils/ajax-stream.js';
import { import {
types, csrfSelector,
postThemeComplete, postThemeComplete,
createErrorObservable, postThemeError,
themeSelector, themeSelector,
usernameSelector, types,
csrfSelector usernameSelector
} from './index.js'; } from './index.js';
function persistTheme(theme) { function persistTheme(theme) {
@ -33,7 +31,8 @@ export default function nightModeEpic(
::ofType( ::ofType(
types.fetchUser.complete, types.fetchUser.complete,
types.toggleNightMode, types.toggleNightMode,
types.postThemeComplete types.postTheme.complete,
types.postTheme.error
) )
.map(_.flow(getState, themeSelector)) .map(_.flow(getState, themeSelector))
// catch existing night mode users // catch existing night mode users
@ -54,9 +53,10 @@ export default function nightModeEpic(
const theme = themeSelector(getState()); const theme = themeSelector(getState());
const username = usernameSelector(getState()); const username = usernameSelector(getState());
return postJSON$('/update-my-theme', { _csrf, theme }) return postJSON$('/update-my-theme', { _csrf, theme })
.pluck('updatedTo') .map(postThemeComplete)
.map(theme => postThemeComplete(username, theme)) .catch(err => {
.catch(createErrorObservable); return Observable.of(postThemeError(username, theme, err));
});
}); });
return Observable.merge(toggleBodyClass, postThemeEpic); return Observable.merge(toggleBodyClass, postThemeEpic);

View File

@ -703,9 +703,7 @@ module.exports = function(User) {
); );
return Promise.reject(err); return Promise.reject(err);
} }
return this.update$({ theme }) return this.update$({ theme }).toPromise();
.map({ updatedTo: theme })
.toPromise();
}; };
// deprecated. remove once live // deprecated. remove once live

View File

@ -5,6 +5,7 @@ import {
createValidatorErrorHandler createValidatorErrorHandler
} from '../utils/middleware'; } from '../utils/middleware';
import supportedLanguages from '../../common/utils/supported-languages.js'; import supportedLanguages from '../../common/utils/supported-languages.js';
import { themes } from '../../common/utils/themes.js';
export default function settingsController(app) { export default function settingsController(app) {
const api = app.loopback.Router(); const api = app.loopback.Router();
@ -79,22 +80,29 @@ export default function settingsController(app) {
updateMyCurrentChallenge updateMyCurrentChallenge
); );
const updateMyThemeValidators = [
check('theme')
.isIn(Object.keys(themes))
.withMessage('Theme is invalid.')
];
function updateMyTheme(req, res, next) { function updateMyTheme(req, res, next) {
req.checkBody('theme', 'Theme is invalid.').isLength({ min: 4 });
const { body: { theme } } = req; const { body: { theme } } = req;
const errors = req.validationErrors(true);
if (errors) {
return res.status(403).json({ errors });
}
if (req.user.theme === theme) { if (req.user.theme === theme) {
return res.json({ msg: 'Theme already set' }); return res.sendFlash('info', 'Theme already set');
} }
return req.user.updateTheme('' + theme) return req.user.updateTheme(theme)
.then( .then(
data => res.json(data), () => res.sendFlash('info', 'Your theme has been updated'),
next next
); );
} }
api.post(
'/update-my-theme',
ifNoUser401,
updateMyThemeValidators,
createValidatorErrorHandler('errors'),
updateMyTheme
);
api.post( api.post(
'/toggle-available-for-hire', '/toggle-available-for-hire',
@ -131,11 +139,6 @@ export default function settingsController(app) {
ifNoUser401, ifNoUser401,
updateMyLang updateMyLang
); );
api.post(
'/update-my-theme',
ifNoUser401,
updateMyTheme
);
app.use(api); app.use(api);
} }

View File

@ -19,13 +19,14 @@ export function wrapHandledError(err, {
} }
// for use with express-validator error formatter // for use with express-validator error formatter
export const createValidatorErrorFormatter = (type, redirectTo, status) => export const createValidatorErrorFormatter = (type, redirectTo) =>
({ msg }) => wrapHandledError( ({ msg }) => wrapHandledError(
new Error(msg), new Error(msg),
{ {
type, type,
message: msg, message: msg,
redirectTo, redirectTo,
status // we default to 400 as these are malformed requests
status: 400
} }
); );