/
fetch-challenges-epic.js
121 lines (110 loc) · 3.17 KB
/
fetch-challenges-epic.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
import { Observable } from 'rx';
import { combineEpics, ofType } from 'redux-epic';
import debug from 'debug';
import {
types,
createErrorObservable,
delayedRedirect,
fetchChallengeCompleted,
fetchChallengesCompleted,
fetchNewBlock,
challengeSelector,
nextChallengeSelector
} from './';
import {
isChallengeLoaded,
fullBlocksSelector
} from '../entities';
import { shapeChallenges } from './utils';
import { types as challenge } from '../routes/Challenges/redux';
import { langSelector } from '../Router/redux';
const isDev = debug.enabled('fcc:*');
function fetchChallengeEpic(actions, { getState }, { services }) {
return actions::ofType(challenge.onRouteChallenges)
.filter(({ payload }) => !isChallengeLoaded(getState(), payload))
.flatMapLatest(({ payload: params }) => {
const options = {
service: 'challenge',
params
};
return services.readService$(options)
.retry(3)
.map(({ entities, ...rest }) => ({
entities: shapeChallenges(entities, isDev),
...rest
}))
.flatMap(({ entities, result, redirect } = {}) => {
const actions = [
fetchChallengeCompleted({
entities,
currentChallenge: result.challenge,
challenge: entities.challenge[result.challenge],
result
}),
redirect ? delayedRedirect(redirect) : null
];
return Observable.from(actions).filter(Boolean);
})
.catch(createErrorObservable);
});
}
export function fetchChallengesForBlockEpic(
actions,
{ getState },
{ services }
) {
return actions::ofType(
types.appMounted,
types.updateChallenges,
types.fetchNewBlock.start
)
.flatMapLatest(({ type, payload }) => {
const fetchAnotherBlock = type === types.fetchNewBlock.start;
const state = getState();
let {
block: blockName = 'basic-html-and-html5'
} = challengeSelector(state);
const lang = langSelector(state);
if (fetchAnotherBlock) {
const fullBlocks = fullBlocksSelector(state);
if (fullBlocks.includes(payload)) {
return Observable.of(null);
}
blockName = payload;
}
const options = {
params: { lang, blockName },
service: 'challenge'
};
return services.readService$(options)
.retry(3)
.map(fetchChallengesCompleted)
.startWith({ type: types.fetchChallenges.start })
.catch(createErrorObservable);
})
.filter(Boolean);
}
function fetchChallengesForNextBlockEpic(action$, { getState }) {
return action$::ofType(challenge.checkForNextBlock)
.map(() => {
const {
nextChallenge,
isNewBlock,
isNewSuperBlock
} = nextChallengeSelector(getState());
const isNewBlockRequired = (
(isNewBlock || isNewSuperBlock) &&
nextChallenge &&
!nextChallenge.description
);
return isNewBlockRequired ?
fetchNewBlock(nextChallenge.block) :
null;
})
.filter(Boolean);
}
export default combineEpics(
fetchChallengeEpic,
fetchChallengesForBlockEpic,
fetchChallengesForNextBlockEpic
);