fix(current-challenge): Fix current-challenge API

This commit is contained in:
Bouncey
2018-10-28 06:18:13 +00:00
committed by mrugesh mohapatra
parent e7316e4567
commit dace86663f
4 changed files with 144 additions and 128 deletions

View File

@ -55,23 +55,20 @@
"type": "string"
},
"description": {
"type": "array"
},
"image": {
"type": "string"
},
"tests": {
"type": "array"
},
"head": {
"type": "array",
"type": "string",
"description": "Appended to user code",
"default": []
"default": ""
},
"tail": {
"type": "array",
"type": "string",
"description": "Prepended to user code",
"default": []
"default": ""
},
"helpRoom": {
"type": "string",

View File

@ -10,31 +10,26 @@ import debug from 'debug';
import accepts from 'accepts';
import dedent from 'dedent';
import { ifNoUserSend } from '../utils/middleware';
import { getChallengeById, cachedMap } from '../utils/map';
import { dasherize } from '../utils';
import { homeLocation } from '../../../config/env.json';
import { ifNoUserSend } from '../utils/middleware';
import { dasherize } from '../utils';
import pathMigrations from '../resources/pathMigration.json';
import { fixCompletedChallengeItem } from '../../common/utils';
const log = debug('fcc:boot:challenges');
const learnURL = 'https://learn.freecodecamp.org';
const learnURL = `${homeLocation}/learn`;
const jsProjects = [
'aaa48de84e1ecc7c742e1124',
'a7f4d8f2483413a6ce226cac',
'56533eb9ac21ba0edf2244e2',
'aff0395860f5d3034dc0bfc9',
'aa2e6f85cab2ab736c9a9b24'
'aaa48de84e1ecc7c742e1124',
'a7f4d8f2483413a6ce226cac',
'56533eb9ac21ba0edf2244e2',
'aff0395860f5d3034dc0bfc9',
'aa2e6f85cab2ab736c9a9b24'
];
function buildUserUpdate(
user,
challengeId,
_completedChallenge,
timezone
) {
function buildUserUpdate(user, challengeId, _completedChallenge, timezone) {
const { files } = _completedChallenge;
let completedChallenge = {};
@ -43,17 +38,9 @@ function buildUserUpdate(
..._completedChallenge,
files: Object.keys(files)
.map(key => files[key])
.map(file => _.pick(
file,
[
'contents',
'key',
'index',
'name',
'path',
'ext'
]
))
.map(file =>
_.pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext'])
)
};
} else {
completedChallenge = _.omit(_completedChallenge, ['files']);
@ -110,11 +97,54 @@ function buildUserUpdate(
};
}
export default function(app) {
function buildChallengeUrl(challenge) {
const { superBlock, block, dashedName } = challenge;
return `/learn/${dasherize(superBlock)}/${dasherize(block)}/${dashedName}`;
}
function getFirstChallenge(Challenge) {
return new Promise(resolve => {
Challenge.find(
{ where: { challengeOrder: 0, superOrder: 1, order: 0 } },
(err, challenge) => {
if (err) {
console.log(err);
return resolve('/learn');
}
return resolve(buildChallengeUrl(challenge));
}
);
});
}
async function createChallengeUrlResolver(app) {
const { Challenge } = app.models;
const cache = new Map();
const firstChallenge = await getFirstChallenge(Challenge);
return function resolveChallengeUrl(id) {
return new Promise(resolve => {
if (cache.has(id)) {
return resolve(cache.get(id));
}
return Challenge.findById(id, (err, challenge) => {
if (err) {
console.log(err);
return firstChallenge;
}
const challengeUrl = buildChallengeUrl(challenge);
cache.set(id, challengeUrl);
return resolve(challengeUrl);
});
});
};
}
export default async function bootChallenge(app, done) {
const send200toNonUser = ifNoUserSend(true);
const api = app.loopback.Router();
const router = app.loopback.Router();
const map = cachedMap(app.models);
const challengeUrlResolver = await createChallengeUrlResolver(app);
api.post(
'/modern-challenge-completed',
@ -124,17 +154,9 @@ export default function(app) {
// deprecate endpoint
// remove once new endpoint is live
api.post(
'/completed-challenge',
send200toNonUser,
completedChallenge
);
api.post('/completed-challenge', send200toNonUser, completedChallenge);
api.post(
'/challenge-completed',
send200toNonUser,
completedChallenge
);
api.post('/challenge-completed', send200toNonUser, completedChallenge);
// deprecate endpoint
// remove once new endpoint is live
@ -144,11 +166,7 @@ export default function(app) {
projectCompleted
);
api.post(
'/project-completed',
send200toNonUser,
projectCompleted
);
api.post('/project-completed', send200toNonUser, projectCompleted);
api.post(
'/backend-challenge-completed',
@ -156,10 +174,7 @@ export default function(app) {
backendChallengeCompleted
);
router.get(
'/challenges/current-challenge',
redirectToCurrentChallenge
);
router.get('/challenges/current-challenge', redirectToCurrentChallenge);
router.get('/challenges', redirectToLearn);
@ -187,26 +202,22 @@ export default function(app) {
}
const user = req.user;
return user.getCompletedChallenges$()
return user
.getCompletedChallenges$()
.flatMap(() => {
const completedDate = Date.now();
const {
id,
files
} = req.body;
const { id, files } = req.body;
const {
alreadyCompleted,
updateData
} = buildUserUpdate(
user,
const { alreadyCompleted, updateData } = buildUserUpdate(user, id, {
id,
{ id, files, completedDate }
);
files,
completedDate
});
const points = alreadyCompleted ? user.points : user.points + 1;
return user.update$(updateData)
return user
.update$(updateData)
.doOnNext(() => user.manualReload())
.doOnNext(({ count }) => log('%s documents updated', count))
.map(() => {
@ -237,15 +248,13 @@ export default function(app) {
return res.sendStatus(403);
}
return req.user.getCompletedChallenges$()
return req.user
.getCompletedChallenges$()
.flatMap(() => {
const completedDate = Date.now();
const { id, solution, timezone, files } = req.body;
const {
alreadyCompleted,
updateData
} = buildUserUpdate(
const { alreadyCompleted, updateData } = buildUserUpdate(
req.user,
id,
{ id, solution, completedDate, files },
@ -255,7 +264,8 @@ export default function(app) {
const user = req.user;
const points = alreadyCompleted ? user.points : user.points + 1;
return user.update$(updateData)
return user
.update$(updateData)
.doOnNext(({ count }) => log('%s documents updated', count))
.map(() => {
if (type === 'json') {
@ -289,36 +299,38 @@ export default function(app) {
const { user, body = {} } = req;
const completedChallenge = _.pick(
body,
[ 'id', 'solution', 'githubLink', 'challengeType', 'files' ]
);
const completedChallenge = _.pick(body, [
'id',
'solution',
'githubLink',
'challengeType',
'files'
]);
completedChallenge.completedDate = Date.now();
if (
!completedChallenge.solution ||
// only basejumps require github links
(
completedChallenge.challengeType === 4 &&
!completedChallenge.githubLink
)
(completedChallenge.challengeType === 4 && !completedChallenge.githubLink)
) {
req.flash(
'danger',
'You haven\'t supplied the necessary URLs for us to inspect your work.'
"You haven't supplied the necessary URLs for us to inspect your work."
);
return res.sendStatus(403);
}
return user.getCompletedChallenges$()
return user
.getCompletedChallenges$()
.flatMap(() => {
const {
alreadyCompleted,
updateData
} = buildUserUpdate(user, completedChallenge.id, completedChallenge);
const { alreadyCompleted, updateData } = buildUserUpdate(
user,
completedChallenge.id,
completedChallenge
);
return user.update$(updateData)
return user
.update$(updateData)
.doOnNext(() => user.manualReload())
.doOnNext(({ count }) => log('%s documents updated', count))
.doOnNext(() => {
@ -352,21 +364,20 @@ export default function(app) {
const { user, body = {} } = req;
const completedChallenge = _.pick(
body,
[ 'id', 'solution' ]
);
const completedChallenge = _.pick(body, ['id', 'solution']);
completedChallenge.completedDate = Date.now();
return user.getCompletedChallenges$()
return user
.getCompletedChallenges$()
.flatMap(() => {
const {
alreadyCompleted,
updateData
} = buildUserUpdate(user, completedChallenge.id, completedChallenge);
const { alreadyCompleted, updateData } = buildUserUpdate(
user,
completedChallenge.id,
completedChallenge
);
return user.update$(updateData)
return user
.update$(updateData)
.doOnNext(({ count }) => log('%s documents updated', count))
.doOnNext(() => {
if (type === 'json') {
@ -382,27 +393,22 @@ export default function(app) {
.subscribe(() => {}, next);
}
function redirectToCurrentChallenge(req, res, next) {
async function redirectToCurrentChallenge(req, res, next) {
const { user } = req;
const challengeId = user && user.currentChallengeId;
return getChallengeById(map, challengeId)
.map(challenge => {
const { block, dashedName, superBlock } = challenge;
if (!dashedName || !block) {
// this should normally not be hit if database is properly seeded
throw new Error(dedent`
Attempted to find '${dashedName}'
from '${ challengeId || 'no challenge id found'}'
but came up empty.
db may not be properly seeded.
`);
}
return `${learnURL}/${dasherize(superBlock)}/${block}/${dashedName}`;
})
.subscribe(
redirect => res.redirect(redirect || learnURL),
next
);
log(req.user.username);
log(challengeId);
const challengeUrl = await challengeUrlResolver(challengeId).catch(next);
log(challengeUrl);
if (challengeUrl === '/learn') {
// this should normally not be hit if database is properly seeded
throw new Error(dedent`
Attempted to find the url for ${challengeId}'
but came up empty.
db may not be properly seeded.
`);
}
return res.redirect(`${homeLocation}${challengeUrl}`);
}
function redirectToLearn(req, res) {
@ -413,4 +419,5 @@ export default function(app) {
}
return res.status(302).redirect(learnURL);
}
done();
}

View File

@ -4,6 +4,8 @@ const readDirP = require('readdirp-walk');
const { parseMarkdown } = require('@freecodecamp/challenge-md-parser');
const { dasherize } = require('./utils');
const challengesDir = path.resolve(__dirname, './challenges');
exports.getChallengesForLang = function getChallengesForLang(lang) {
@ -57,6 +59,7 @@ async function buildCurriculum(file, curriculum) {
);
const { name: blockName, order, superOrder } = meta;
challenge.block = blockName;
challenge.dashedName = dasherize(challenge.title);
challenge.order = order;
challenge.superOrder = superOrder;
challenge.superBlock = superBlock;

View File

@ -1,7 +1,7 @@
const path = require('path');
const fs = require('fs');
require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
const MongoClient = require('mongodb').MongoClient;
const { MongoClient, ObjectID } = require('mongodb');
const { getChallengesForLang } = require('@freecodecamp/curriculum');
const { flatten } = require('lodash');
const debug = require('debug');
@ -9,7 +9,7 @@ const debug = require('debug');
const { createPathMigrationMap } = require('./createPathMigrationMap');
const log = debug('fcc:tools:seedChallenges');
const { MONGOHQ_URL, LOCALE: lang } = process.env;
const { MONGOHQ_URL, LOCALE: lang = 'english' } = process.env;
function handleError(err, client) {
if (err) {
@ -32,14 +32,16 @@ MongoClient.connect(
function(err, client) {
handleError(err, client);
log('Connected successfully to mongo');
log('Connected successfully to mongo at %s', MONGOHQ_URL);
const db = client.db('freecodecamp');
const challenges = db.collection('challenge');
const challengeCollection = db.collection('challenge');
challenges.deleteMany({}, err => {
challengeCollection.deleteMany({}, err => {
handleError(err, client);
log('deleted all the challenges');
const curriculum = getChallengesForLang(lang);
const allChallenges = Object.keys(curriculum)
@ -51,18 +53,25 @@ MongoClient.connect(
return [...challengeArray, ...flatten(challengesForBlock)];
}, [])
.map(challenge => {
challenge._id = challenge.id.slice(0);
const currentId = challenge.id.slice(0);
challenge._id = ObjectID(currentId);
delete challenge.id;
return challenge;
});
try {
challenges.insertMany(allChallenges, { ordered: false });
challengeCollection.insertMany(
allChallenges,
{ ordered: false },
err => {
handleError(err, client);
log('challenge seed complete');
client.close();
}
);
} catch (e) {
handleError(e, client);
} finally {
log('challenge seed complete');
client.close();
log('generating path migration map');
const pathMap = createPathMigrationMap(curriculum);
const outputDir = path.resolve(