feat: Create and use dynamic active donation counts
This commit is contained in:
committed by
mrugesh mohapatra
parent
331ea3ebf9
commit
8fab33ba99
@ -15,57 +15,73 @@ import { ifNoUser401, ifNoUserRedirectTo } from '../utils/middleware';
|
|||||||
const log = debugFactory('fcc:boot:user');
|
const log = debugFactory('fcc:boot:user');
|
||||||
const sendNonUserToHome = ifNoUserRedirectTo(homeLocation);
|
const sendNonUserToHome = ifNoUserRedirectTo(homeLocation);
|
||||||
|
|
||||||
module.exports = function bootUser(app) {
|
function bootUser(app) {
|
||||||
const api = app.loopback.Router();
|
const api = app.loopback.Router();
|
||||||
|
|
||||||
|
const getSessionUser = createReadSessionUser(app);
|
||||||
|
const postReportUserProfile = createPostReportUserProfile(app);
|
||||||
|
const postDeleteAccount = createPostDeleteAccount(app);
|
||||||
|
|
||||||
api.get('/account', sendNonUserToHome, getAccount);
|
api.get('/account', sendNonUserToHome, getAccount);
|
||||||
api.get('/account/unlink/:social', sendNonUserToHome, getUnlinkSocial);
|
api.get('/account/unlink/:social', sendNonUserToHome, getUnlinkSocial);
|
||||||
api.get('/user/get-session-user', readSessionUser);
|
api.get('/user/get-session-user', getSessionUser);
|
||||||
|
|
||||||
api.post('/account/delete', ifNoUser401, createPostDeleteAccount(app));
|
api.post('/account/delete', ifNoUser401, postDeleteAccount);
|
||||||
api.post('/account/reset-progress', ifNoUser401, postResetProgress);
|
api.post('/account/reset-progress', ifNoUser401, postResetProgress);
|
||||||
api.post('/user/report-user/', ifNoUser401, createPostReportUserProfile(app));
|
api.post('/user/report-user/', ifNoUser401, postReportUserProfile);
|
||||||
|
|
||||||
app.use('/internal', api);
|
app.use('/internal', api);
|
||||||
};
|
}
|
||||||
|
|
||||||
function readSessionUser(req, res, next) {
|
function createReadSessionUser(app) {
|
||||||
const queryUser = req.user;
|
const { Donation } = app.models;
|
||||||
|
|
||||||
const source =
|
return function getSessionUser(req, res, next) {
|
||||||
queryUser &&
|
const queryUser = req.user;
|
||||||
Observable.forkJoin(
|
|
||||||
queryUser.getCompletedChallenges$(),
|
const source =
|
||||||
queryUser.getPoints$(),
|
queryUser &&
|
||||||
(completedChallenges, progressTimestamps) => ({
|
Observable.forkJoin(
|
||||||
completedChallenges,
|
queryUser.getCompletedChallenges$(),
|
||||||
progress: getProgress(progressTimestamps, queryUser.timezone)
|
queryUser.getPoints$(),
|
||||||
})
|
Donation.getCurrentActiveDonationCount$(),
|
||||||
);
|
(completedChallenges, progressTimestamps, activeDonations) => ({
|
||||||
Observable.if(
|
activeDonations,
|
||||||
() => !queryUser,
|
completedChallenges,
|
||||||
Observable.of({ user: {}, result: '' }),
|
progress: getProgress(progressTimestamps, queryUser.timezone)
|
||||||
Observable.defer(() => source)
|
})
|
||||||
.map(({ completedChallenges, progress }) => ({
|
);
|
||||||
...queryUser.toJSON(),
|
Observable.if(
|
||||||
...progress,
|
() => !queryUser,
|
||||||
completedChallenges: completedChallenges.map(fixCompletedChallengeItem)
|
Observable.of({ user: {}, result: '' }),
|
||||||
}))
|
Observable.defer(() => source)
|
||||||
.map(user => ({
|
.map(({ activeDonations, completedChallenges, progress }) => ({
|
||||||
user: {
|
user: {
|
||||||
[user.username]: {
|
...queryUser.toJSON(),
|
||||||
...pick(user, userPropsForSession),
|
...progress,
|
||||||
isEmailVerified: !!user.emailVerified,
|
completedChallenges: completedChallenges.map(
|
||||||
isGithub: !!user.githubProfile,
|
fixCompletedChallengeItem
|
||||||
isLinkedIn: !!user.linkedin,
|
)
|
||||||
isTwitter: !!user.twitter,
|
},
|
||||||
isWebsite: !!user.website,
|
sessionMeta: { activeDonations }
|
||||||
...normaliseUserFields(user)
|
}))
|
||||||
}
|
.map(({ user, sessionMeta }) => ({
|
||||||
},
|
user: {
|
||||||
result: user.username
|
[user.username]: {
|
||||||
}))
|
...pick(user, userPropsForSession),
|
||||||
).subscribe(user => res.json(user), next);
|
isEmailVerified: !!user.emailVerified,
|
||||||
|
isGithub: !!user.githubProfile,
|
||||||
|
isLinkedIn: !!user.linkedin,
|
||||||
|
isTwitter: !!user.twitter,
|
||||||
|
isWebsite: !!user.website,
|
||||||
|
...normaliseUserFields(user)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
sessionMeta,
|
||||||
|
result: user.username
|
||||||
|
}))
|
||||||
|
).subscribe(user => res.json(user), next);
|
||||||
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function getAccount(req, res) {
|
function getAccount(req, res) {
|
||||||
@ -241,3 +257,4 @@ function createPostReportUserProfile(app) {
|
|||||||
);
|
);
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
export default bootUser;
|
||||||
|
@ -2,14 +2,18 @@ import { Observable } from 'rx';
|
|||||||
|
|
||||||
export default function(Donation) {
|
export default function(Donation) {
|
||||||
Donation.on('dataSourceAttached', () => {
|
Donation.on('dataSourceAttached', () => {
|
||||||
|
Donation.find$ = Observable.fromNodeCallback(Donation.find.bind(Donation));
|
||||||
Donation.findOne$ = Observable.fromNodeCallback(
|
Donation.findOne$ = Observable.fromNodeCallback(
|
||||||
Donation.findOne.bind(Donation)
|
Donation.findOne.bind(Donation)
|
||||||
);
|
);
|
||||||
Donation.prototype.validate$ = Observable.fromNodeCallback(
|
|
||||||
Donation.prototype.validate
|
|
||||||
);
|
|
||||||
Donation.prototype.destroy$ = Observable.fromNodeCallback(
|
|
||||||
Donation.prototype.destroy
|
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
function getCurrentActiveDonationCount$() {
|
||||||
|
// eslint-disable-next-line no-undefined
|
||||||
|
return Donation.find$({ where: { endDate: undefined } }).map(
|
||||||
|
instances => instances.length
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
Donation.getCurrentActiveDonationCount$ = getCurrentActiveDonationCount$;
|
||||||
}
|
}
|
||||||
|
@ -6,9 +6,12 @@ import FullWidthRow from '../components/helpers/FullWidthRow';
|
|||||||
|
|
||||||
import './supporters.css';
|
import './supporters.css';
|
||||||
|
|
||||||
const propTypes = { isDonating: PropTypes.bool.isRequired };
|
const propTypes = {
|
||||||
|
activeDonations: PropTypes.number.isRequired,
|
||||||
|
isDonating: PropTypes.bool.isRequired
|
||||||
|
};
|
||||||
|
|
||||||
function Supporters({ isDonating }) {
|
function Supporters({ isDonating, activeDonations }) {
|
||||||
return (
|
return (
|
||||||
<Fragment>
|
<Fragment>
|
||||||
<FullWidthRow>
|
<FullWidthRow>
|
||||||
@ -16,10 +19,10 @@ function Supporters({ isDonating }) {
|
|||||||
</FullWidthRow>
|
</FullWidthRow>
|
||||||
<FullWidthRow>
|
<FullWidthRow>
|
||||||
<div id='supporter-progress-wrapper'>
|
<div id='supporter-progress-wrapper'>
|
||||||
<ProgressBar max={10000} now={400} />
|
<ProgressBar max={10000} now={activeDonations} />
|
||||||
<div id='progress-label-wrapper'>
|
<div id='progress-label-wrapper'>
|
||||||
<span className='progress-label'>
|
<span className='progress-label'>
|
||||||
4000 supporters out of 10,000 goal
|
{activeDonations} supporters out of 10,000 goal
|
||||||
</span>
|
</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
@ -14,13 +14,15 @@ import Supporters from '../components/Supporters';
|
|||||||
import {
|
import {
|
||||||
userSelector,
|
userSelector,
|
||||||
userFetchStateSelector,
|
userFetchStateSelector,
|
||||||
isSignedInSelector
|
isSignedInSelector,
|
||||||
|
activeDonationsSelector
|
||||||
} from '../redux';
|
} from '../redux';
|
||||||
import { randomQuote } from '../utils/get-words';
|
import { randomQuote } from '../utils/get-words';
|
||||||
|
|
||||||
import './welcome.css';
|
import './welcome.css';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
|
activedonations: PropTypes.number,
|
||||||
fetchState: PropTypes.shape({
|
fetchState: PropTypes.shape({
|
||||||
pending: PropTypes.bool,
|
pending: PropTypes.bool,
|
||||||
complete: PropTypes.bool,
|
complete: PropTypes.bool,
|
||||||
@ -42,7 +44,13 @@ const mapStateToProps = createSelector(
|
|||||||
userFetchStateSelector,
|
userFetchStateSelector,
|
||||||
isSignedInSelector,
|
isSignedInSelector,
|
||||||
userSelector,
|
userSelector,
|
||||||
(fetchState, isSignedIn, user) => ({ fetchState, isSignedIn, user })
|
activeDonationsSelector,
|
||||||
|
(fetchState, isSignedIn, user, activeDonations) => ({
|
||||||
|
activeDonations,
|
||||||
|
fetchState,
|
||||||
|
isSignedIn,
|
||||||
|
user
|
||||||
|
})
|
||||||
);
|
);
|
||||||
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
|
const mapDispatchToProps = dispatch => bindActionCreators({}, dispatch);
|
||||||
|
|
||||||
@ -57,7 +65,8 @@ function Welcome({
|
|||||||
completedCertCount = 0,
|
completedCertCount = 0,
|
||||||
completedLegacyCertCount: completedLegacyCerts = 0,
|
completedLegacyCertCount: completedLegacyCerts = 0,
|
||||||
isDonating
|
isDonating
|
||||||
}
|
},
|
||||||
|
activeDonations
|
||||||
}) {
|
}) {
|
||||||
if (pending && !complete) {
|
if (pending && !complete) {
|
||||||
return (
|
return (
|
||||||
@ -94,7 +103,10 @@ function Welcome({
|
|||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
<Spacer />
|
<Spacer />
|
||||||
<Supporters isDonating={isDonating} />
|
<Supporters
|
||||||
|
activeDonations={activeDonations}
|
||||||
|
isDonating={isDonating}
|
||||||
|
/>
|
||||||
<Spacer size={2} />
|
<Spacer size={2} />
|
||||||
<Row>
|
<Row>
|
||||||
<Col sm={8} smOffset={2} xs={12}>
|
<Col sm={8} smOffset={2} xs={12}>
|
||||||
|
@ -16,10 +16,12 @@ function* fetchSessionUser() {
|
|||||||
}
|
}
|
||||||
try {
|
try {
|
||||||
const {
|
const {
|
||||||
data: { user = {}, result = '' }
|
data: { user = {}, result = '', sessionMeta = {} }
|
||||||
} = yield call(getSessionUser);
|
} = yield call(getSessionUser);
|
||||||
const appUser = user[result] || {};
|
const appUser = user[result] || {};
|
||||||
yield put(fetchUserComplete({ user: appUser, username: result }));
|
yield put(
|
||||||
|
fetchUserComplete({ user: appUser, username: result, sessionMeta })
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
yield put(fetchUserError(e));
|
yield put(fetchUserError(e));
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ const initialState = {
|
|||||||
userProfileFetchState: {
|
userProfileFetchState: {
|
||||||
...defaultFetchState
|
...defaultFetchState
|
||||||
},
|
},
|
||||||
|
sessionMeta: {},
|
||||||
showDonationModal: false,
|
showDonationModal: false,
|
||||||
isOnline: true
|
isOnline: true
|
||||||
};
|
};
|
||||||
@ -158,6 +159,13 @@ export const userSelector = state => {
|
|||||||
return state[ns].user[username] || {};
|
return state[ns].user[username] || {};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export const sessionMetaSelector = state => state[ns].sessionMeta;
|
||||||
|
export const activeDonationsSelector = state =>
|
||||||
|
// this default is mostly for development where there are likely no donators
|
||||||
|
// in the local db
|
||||||
|
// If we see this in production then things are getting weird
|
||||||
|
sessionMetaSelector(state).activeDonations || 4040;
|
||||||
|
|
||||||
function spreadThePayloadOnUser(state, payload) {
|
function spreadThePayloadOnUser(state, payload) {
|
||||||
return {
|
return {
|
||||||
...state,
|
...state,
|
||||||
@ -181,7 +189,10 @@ export const reducer = handleActions(
|
|||||||
...state,
|
...state,
|
||||||
userProfileFetchState: { ...defaultFetchState }
|
userProfileFetchState: { ...defaultFetchState }
|
||||||
}),
|
}),
|
||||||
[types.fetchUserComplete]: (state, { payload: { user, username } }) => ({
|
[types.fetchUserComplete]: (
|
||||||
|
state,
|
||||||
|
{ payload: { user, username, sessionMeta } }
|
||||||
|
) => ({
|
||||||
...state,
|
...state,
|
||||||
user: {
|
user: {
|
||||||
...state.user,
|
...state.user,
|
||||||
@ -193,6 +204,10 @@ export const reducer = handleActions(
|
|||||||
complete: true,
|
complete: true,
|
||||||
errored: false,
|
errored: false,
|
||||||
error: null
|
error: null
|
||||||
|
},
|
||||||
|
sessionMeta: {
|
||||||
|
...state.sessionMeta,
|
||||||
|
...sessionMeta
|
||||||
}
|
}
|
||||||
}),
|
}),
|
||||||
[types.fetchUserError]: (state, { payload }) => ({
|
[types.fetchUserError]: (state, { payload }) => ({
|
||||||
|
Reference in New Issue
Block a user