feat(mapUi): Create mapUi specific service

This commit is contained in:
Stuart Taylor
2018-02-20 13:07:32 +00:00
committed by Stuart Taylor
parent e0d084465e
commit 1d420b835c
7 changed files with 158 additions and 50 deletions

View File

@ -0,0 +1,46 @@
import { ofType } from 'redux-epic';
import debug from 'debug';
import {
types as appTypes,
createErrorObservable
} from '../../redux';
import { types, fetchMapUiComplete } from './';
import { langSelector } from '../../Router/redux';
import { shapeChallenges } from '../../redux/utils';
const isDev = debug.enabled('fcc:*');
export default function fetchMapUiEpic(
actions,
{ getState },
{ services }
) {
return actions::ofType(
appTypes.appMounted
)
.flatMapLatest(() => {
const lang = langSelector(getState());
const options = {
params: { lang },
service: 'map-ui'
};
return services.readService$(options)
.retry(3)
.map(({ entities, ...res }) => ({
entities: shapeChallenges(
entities,
isDev
),
...res
}))
.map(({ entities, result } = {}) => {
return fetchMapUiComplete(
entities,
result
);
})
.startWith({ type: types.fetchMapUi.start })
.catch(createErrorObservable);
});
}

View File

@ -1,25 +1,26 @@
import { import {
createAction, createAction,
createAsyncTypes,
createTypes, createTypes,
handleActions handleActions
} from 'berkeleys-redux-utils'; } from 'berkeleys-redux-utils';
import { createSelector } from 'reselect'; import { createSelector } from 'reselect';
import noop from 'lodash/noop'; import { capitalize, noop} from 'lodash';
import capitalize from 'lodash/capitalize';
import * as utils from './utils.js'; import * as utils from './utils.js';
import ns from '../ns.json'; import ns from '../ns.json';
import { import {
types as app,
createEventMetaCreator createEventMetaCreator
} from '../../redux'; } from '../../redux';
export const epics = []; import fewtchMapUiEpic from './fetch-map-ui-epic';
export const epics = [ fewtchMapUiEpic ];
export const types = createTypes([ export const types = createTypes([
'onRouteMap', 'onRouteMap',
'initMap', 'initMap',
createAsyncTypes('fetchMapUi'),
'toggleThisPanel', 'toggleThisPanel',
'isAllCollapsed', 'isAllCollapsed',
@ -31,6 +32,12 @@ export const types = createTypes([
export const initMap = createAction(types.initMap); export const initMap = createAction(types.initMap);
export const fetchMapUiComplete = createAction(
types.fetchMapUi.complete,
(entities, result) => ({ entities, result }),
entities => ({ entities })
);
export const toggleThisPanel = createAction(types.toggleThisPanel); export const toggleThisPanel = createAction(types.toggleThisPanel);
export const collapseAll = createAction(types.collapseAll); export const collapseAll = createAction(types.collapseAll);
@ -100,7 +107,7 @@ export default handleActions(
mapUi mapUi
}; };
}, },
[app.fetchChallenges.complete]: (state, { payload }) => { [types.fetchMapUi.complete]: (state, { payload }) => {
const { entities, result } = payload; const { entities, result } = payload;
return { return {
...state, ...state,

View File

@ -1,4 +1,4 @@
import { findIndex, invert, pick, property } from 'lodash'; import { findIndex, invert, pick, property, merge } from 'lodash';
import uuid from 'uuid/v4'; import uuid from 'uuid/v4';
import { import {
composeReducers, composeReducers,
@ -162,10 +162,7 @@ export default composeReducers(
} }
}; };
} }
return { return merge(state, action.meta.entities);
...state,
...action.meta.entities
};
} }
return state; return state;
}, },

View File

@ -19,7 +19,7 @@ import { langSelector } from '../Router/redux';
const isDev = debug.enabled('fcc:*'); const isDev = debug.enabled('fcc:*');
export function fetchChallengeEpic(actions, { getState }, { services }) { export default function fetchChallengeEpic(actions, { getState }, { services }) {
return actions::ofType(challenge.onRouteChallenges) return actions::ofType(challenge.onRouteChallenges)
.filter(({ payload }) => !isChallengeLoaded(getState(), payload)) .filter(({ payload }) => !isChallengeLoaded(getState(), payload))
.flatMapLatest(({ payload: params }) => { .flatMapLatest(({ payload: params }) => {
@ -84,4 +84,3 @@ export function fetchChallengesEpic(
}); });
} }
export default combineEpics(fetchChallengeEpic, fetchChallengesEpic);

View File

@ -1,12 +1,15 @@
import Fetchr from 'fetchr'; import Fetchr from 'fetchr';
import getUserServices from '../services/user'; import getUserServices from '../services/user';
import getMapServices from '../services/map'; import getMapServices from '../services/map';
import getMapUiServices from '../services/mapUi';
export default function bootServices(app) { export default function bootServices(app) {
const userServices = getUserServices(app); const userServices = getUserServices(app);
const mapServices = getMapServices(app); const mapServices = getMapServices(app);
const mapUiServices = getMapUiServices(app);
Fetchr.registerFetcher(userServices); Fetchr.registerFetcher(userServices);
Fetchr.registerFetcher(mapServices); Fetchr.registerFetcher(mapServices);
Fetchr.registerFetcher(mapUiServices);
app.use('/services', Fetchr.middleware()); app.use('/services', Fetchr.middleware());
} }

56
server/services/mapUi.js Normal file
View File

@ -0,0 +1,56 @@
import debug from 'debug';
import { Observable } from 'rx';
import { cachedMap, getMapForLang } from '../utils/map';
const log = debug('fcc:services:mapUi');
export default function mapUiService(app) {
const challengeMap = cachedMap(app.models);
return {
name: 'map-ui',
read: function readMapUi(req, resource, { lang = 'en' } = {}, config, cb) {
log(`generating mapUi for ${lang}`);
return challengeMap.map(getMapForLang(lang))
.flatMap(({
result: { superBlocks },
entities: {
superBlock: fullSuperBlockMap,
block: fullBlockMap,
challenge: fullChallengeMap
}
}) => {
const superBlockMap = superBlocks
.map(superBlock => fullSuperBlockMap[superBlock])
.reduce((map, { dashedName, blocks, title }) => {
map[dashedName] = { blocks, title, dashedName};
return map;
}, {});
const blockMap = Object.keys(fullBlockMap)
.map(block => fullBlockMap[block])
.reduce((map, { dashedName, title, time, challenges }) => {
map[dashedName] = { dashedName, title, time, challenges };
return map;
}, {});
const challengeMap = Object.keys(fullChallengeMap)
.map(challenge => fullChallengeMap[challenge])
.reduce((map, { dashedName, name, id}) => {
map[dashedName] = {name, dashedName, id};
return map;
}, {});
return Observable.of({
result: { superBlocks },
entities: {
superBlock: superBlockMap,
block: blockMap,
challenge: challengeMap
}
});
}).subscribe(
mapUi => cb(null, mapUi ),
err => { log(err); return cb(err); }
);
}
};
}

View File

@ -74,43 +74,43 @@ export function _cachedMap({ Block, Challenge }) {
}, blocksMap); }, blocksMap);
}); });
const superBlockMap = blocks.map(blocks => blocks.reduce((map, block) => { const superBlockMap = blocks.map(blocks => blocks.reduce((map, block) => {
if ( if (
map[block.superBlock] && map[block.superBlock] &&
map[block.superBlock].blocks map[block.superBlock].blocks
) { ) {
map[block.superBlock].blocks.push(block.dashedName); map[block.superBlock].blocks.push(block.dashedName);
} else { } else {
map[block.superBlock] = { map[block.superBlock] = {
title: _.startCase(block.superBlock), title: _.startCase(block.superBlock),
order: block.superOrder, order: block.superOrder,
name: nameify(_.startCase(block.superBlock)), name: nameify(_.startCase(block.superBlock)),
dashedName: block.superBlock, dashedName: block.superBlock,
blocks: [block.dashedName], blocks: [block.dashedName],
message: block.superBlockMessage message: block.superBlockMessage
}; };
} }
return map; return map;
}, {})); }, {}));
const superBlocks = superBlockMap.map(superBlockMap => { const superBlocks = superBlockMap.map(superBlockMap => {
return Object.keys(superBlockMap) return Object.keys(superBlockMap)
.map(key => superBlockMap[key]) .map(key => superBlockMap[key])
.map(({ dashedName }) => dashedName); .map(({ dashedName }) => dashedName);
}); });
return Observable.combineLatest( return Observable.combineLatest(
superBlockMap, superBlockMap,
blockMap, blockMap,
challengeMap, challengeMap,
superBlocks, superBlocks,
(superBlock, block, challenge, superBlocks) => ({ (superBlock, block, challenge, superBlocks) => ({
entities: { entities: {
superBlock, superBlock,
block, block,
challenge challenge
}, },
result: { result: {
superBlocks superBlocks
} }
}) })
) )
.do(checkMapData) .do(checkMapData)
.shareReplay(); .shareReplay();