fix(current-challenge): Fix current-challenge API
This commit is contained in:
committed by
mrugesh mohapatra
parent
e7316e4567
commit
dace86663f
@ -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",
|
||||
|
@ -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) {
|
||||
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 '${dashedName}'
|
||||
from '${ challengeId || 'no challenge id found'}'
|
||||
Attempted to find the url for ${challengeId}'
|
||||
but came up empty.
|
||||
db may not be properly seeded.
|
||||
`);
|
||||
}
|
||||
return `${learnURL}/${dasherize(superBlock)}/${block}/${dashedName}`;
|
||||
})
|
||||
.subscribe(
|
||||
redirect => res.redirect(redirect || learnURL),
|
||||
next
|
||||
);
|
||||
return res.redirect(`${homeLocation}${challengeUrl}`);
|
||||
}
|
||||
|
||||
function redirectToLearn(req, res) {
|
||||
@ -413,4 +419,5 @@ export default function(app) {
|
||||
}
|
||||
return res.status(302).redirect(learnURL);
|
||||
}
|
||||
done();
|
||||
}
|
||||
|
@ -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;
|
||||
|
@ -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(
|
||||
|
Reference in New Issue
Block a user