142 lines
3.9 KiB
JavaScript
142 lines
3.9 KiB
JavaScript
import { Observable } from 'rx';
|
|
import { Schema, valuesOf, arrayOf, normalize } from 'normalizr';
|
|
import { nameify, dasherize, unDasherize } from '../utils';
|
|
import debug from 'debug';
|
|
|
|
const isDev = process.env.NODE_ENV !== 'production';
|
|
const isBeta = !!process.env.BETA;
|
|
const challengesRegex = /^(bonfire|waypoint|zipline|basejump|checkpoint)/i;
|
|
const log = debug('fcc:challenges');
|
|
const challenge = new Schema('challenge', { idAttribute: 'dashedName' });
|
|
const block = new Schema('block', { idAttribute: 'dashedName' });
|
|
const superBlock = new Schema('superBlock', { idAttribute: 'dashedName' });
|
|
|
|
block.define({
|
|
challenges: arrayOf(challenge)
|
|
});
|
|
|
|
superBlock.define({
|
|
blocks: arrayOf(block)
|
|
});
|
|
|
|
const mapSchema = valuesOf(superBlock);
|
|
|
|
/*
|
|
* interface ChallengeMap {
|
|
* result: [superBlockDashedName: String]
|
|
* entities: {
|
|
* superBlock: {
|
|
* [superBlockDashedName: String]: {
|
|
* blocks: [blockDashedName: String]
|
|
* }
|
|
* },
|
|
* block: {
|
|
* [blockDashedName: String]: {
|
|
* challenges: [challengeDashedName: String]
|
|
* }
|
|
* },
|
|
* challenge: {
|
|
* [challengeDashedName: String]: Challenge
|
|
* }
|
|
* }
|
|
* }
|
|
*/
|
|
function cachedMap(Block) {
|
|
const query = {
|
|
include: 'challenges',
|
|
order: ['superOrder ASC', 'order ASC']
|
|
};
|
|
return Block.find$(query)
|
|
.flatMap(blocks => Observable.from(blocks.map(block => block.toJSON())))
|
|
.reduce((map, block) => {
|
|
if (map[block.superBlock]) {
|
|
map[block.superBlock].blocks.push(block);
|
|
} else {
|
|
map[block.superBlock] = {
|
|
title: block.superBlock,
|
|
order: block.superOrder,
|
|
name: nameify(block.superBlock),
|
|
dashedName: dasherize(block.superBlock),
|
|
blocks: [block]
|
|
};
|
|
}
|
|
return map;
|
|
}, {})
|
|
.map(map => normalize(map, mapSchema))
|
|
.map(map => {
|
|
const result = Object.keys(map.result).reduce((result, supName) => {
|
|
const index = map.entities.superBlock[supName].order;
|
|
result[index] = supName;
|
|
return result;
|
|
}, []);
|
|
return {
|
|
...map,
|
|
result
|
|
};
|
|
})
|
|
.shareReplay();
|
|
}
|
|
|
|
function shouldNotFilterComingSoon({ isComingSoon, isBeta: challengeIsBeta }) {
|
|
return isDev ||
|
|
!isComingSoon ||
|
|
(isBeta && challengeIsBeta);
|
|
}
|
|
|
|
function getFirstChallenge(challengeMap$) {
|
|
return challengeMap$
|
|
.map(({ entities: { superBlock, block, challenge }, result }) => {
|
|
return challenge[
|
|
block[
|
|
superBlock[
|
|
result[0]
|
|
].blocks[0]
|
|
].challenges[0]
|
|
];
|
|
});
|
|
}
|
|
|
|
function getChallengeByDashedName(dashedName, challengeMap$) {
|
|
const challengeName = unDasherize(dashedName)
|
|
.replace(challengesRegex, '');
|
|
const testChallengeName = new RegExp(challengeName, 'i');
|
|
log('looking for %s', testChallengeName);
|
|
|
|
return challengeMap$
|
|
.map(({ entities }) => entities.challenge)
|
|
.flatMap(challengeMap => {
|
|
return Observable.from(Object.keys(challengeMap))
|
|
.map(key => challengeMap[key]);
|
|
})
|
|
.filter(challenge => {
|
|
return shouldNotFilterComingSoon(challenge) &&
|
|
testChallengeName.test(challenge.name);
|
|
})
|
|
.last({ defaultValue: null })
|
|
.flatMap(challengeOrNull => {
|
|
if (challengeOrNull) {
|
|
return Observable.just(challengeOrNull);
|
|
}
|
|
return getFirstChallenge(challengeMap$);
|
|
})
|
|
.map(challenge => ({
|
|
entities: { challenge: { [challenge.dashedName]: challenge } },
|
|
result: challenge.dashedName
|
|
}));
|
|
}
|
|
|
|
export default function mapService(app) {
|
|
const Block = app.models.Block;
|
|
const challengeMap$ = cachedMap(Block);
|
|
return {
|
|
name: 'map',
|
|
read: (req, resource, { dashedName } = {}, config, cb) => {
|
|
if (dashedName) {
|
|
return getChallengeByDashedName(dashedName, challengeMap$)
|
|
.subscribe(challenge => cb(null, challenge), cb);
|
|
}
|
|
return challengeMap$.subscribe(map => cb(null, map), cb);
|
|
}
|
|
};
|
|
}
|