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"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"description": {
|
"description": {
|
||||||
"type": "array"
|
|
||||||
},
|
|
||||||
"image": {
|
|
||||||
"type": "string"
|
"type": "string"
|
||||||
},
|
},
|
||||||
"tests": {
|
"tests": {
|
||||||
"type": "array"
|
"type": "array"
|
||||||
},
|
},
|
||||||
"head": {
|
"head": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"description": "Appended to user code",
|
"description": "Appended to user code",
|
||||||
"default": []
|
"default": ""
|
||||||
},
|
},
|
||||||
"tail": {
|
"tail": {
|
||||||
"type": "array",
|
"type": "string",
|
||||||
"description": "Prepended to user code",
|
"description": "Prepended to user code",
|
||||||
"default": []
|
"default": ""
|
||||||
},
|
},
|
||||||
"helpRoom": {
|
"helpRoom": {
|
||||||
"type": "string",
|
"type": "string",
|
||||||
|
@ -10,16 +10,16 @@ import debug from 'debug';
|
|||||||
import accepts from 'accepts';
|
import accepts from 'accepts';
|
||||||
import dedent from 'dedent';
|
import dedent from 'dedent';
|
||||||
|
|
||||||
import { ifNoUserSend } from '../utils/middleware';
|
import { homeLocation } from '../../../config/env.json';
|
||||||
import { getChallengeById, cachedMap } from '../utils/map';
|
|
||||||
import { dasherize } from '../utils';
|
|
||||||
|
|
||||||
|
import { ifNoUserSend } from '../utils/middleware';
|
||||||
|
import { dasherize } from '../utils';
|
||||||
import pathMigrations from '../resources/pathMigration.json';
|
import pathMigrations from '../resources/pathMigration.json';
|
||||||
import { fixCompletedChallengeItem } from '../../common/utils';
|
import { fixCompletedChallengeItem } from '../../common/utils';
|
||||||
|
|
||||||
const log = debug('fcc:boot:challenges');
|
const log = debug('fcc:boot:challenges');
|
||||||
|
|
||||||
const learnURL = 'https://learn.freecodecamp.org';
|
const learnURL = `${homeLocation}/learn`;
|
||||||
|
|
||||||
const jsProjects = [
|
const jsProjects = [
|
||||||
'aaa48de84e1ecc7c742e1124',
|
'aaa48de84e1ecc7c742e1124',
|
||||||
@ -29,12 +29,7 @@ const jsProjects = [
|
|||||||
'aa2e6f85cab2ab736c9a9b24'
|
'aa2e6f85cab2ab736c9a9b24'
|
||||||
];
|
];
|
||||||
|
|
||||||
function buildUserUpdate(
|
function buildUserUpdate(user, challengeId, _completedChallenge, timezone) {
|
||||||
user,
|
|
||||||
challengeId,
|
|
||||||
_completedChallenge,
|
|
||||||
timezone
|
|
||||||
) {
|
|
||||||
const { files } = _completedChallenge;
|
const { files } = _completedChallenge;
|
||||||
let completedChallenge = {};
|
let completedChallenge = {};
|
||||||
|
|
||||||
@ -43,17 +38,9 @@ function buildUserUpdate(
|
|||||||
..._completedChallenge,
|
..._completedChallenge,
|
||||||
files: Object.keys(files)
|
files: Object.keys(files)
|
||||||
.map(key => files[key])
|
.map(key => files[key])
|
||||||
.map(file => _.pick(
|
.map(file =>
|
||||||
file,
|
_.pick(file, ['contents', 'key', 'index', 'name', 'path', 'ext'])
|
||||||
[
|
)
|
||||||
'contents',
|
|
||||||
'key',
|
|
||||||
'index',
|
|
||||||
'name',
|
|
||||||
'path',
|
|
||||||
'ext'
|
|
||||||
]
|
|
||||||
))
|
|
||||||
};
|
};
|
||||||
} else {
|
} else {
|
||||||
completedChallenge = _.omit(_completedChallenge, ['files']);
|
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 send200toNonUser = ifNoUserSend(true);
|
||||||
const api = app.loopback.Router();
|
const api = app.loopback.Router();
|
||||||
const router = app.loopback.Router();
|
const router = app.loopback.Router();
|
||||||
const map = cachedMap(app.models);
|
const challengeUrlResolver = await createChallengeUrlResolver(app);
|
||||||
|
|
||||||
api.post(
|
api.post(
|
||||||
'/modern-challenge-completed',
|
'/modern-challenge-completed',
|
||||||
@ -124,17 +154,9 @@ export default function(app) {
|
|||||||
|
|
||||||
// deprecate endpoint
|
// deprecate endpoint
|
||||||
// remove once new endpoint is live
|
// remove once new endpoint is live
|
||||||
api.post(
|
api.post('/completed-challenge', send200toNonUser, completedChallenge);
|
||||||
'/completed-challenge',
|
|
||||||
send200toNonUser,
|
|
||||||
completedChallenge
|
|
||||||
);
|
|
||||||
|
|
||||||
api.post(
|
api.post('/challenge-completed', send200toNonUser, completedChallenge);
|
||||||
'/challenge-completed',
|
|
||||||
send200toNonUser,
|
|
||||||
completedChallenge
|
|
||||||
);
|
|
||||||
|
|
||||||
// deprecate endpoint
|
// deprecate endpoint
|
||||||
// remove once new endpoint is live
|
// remove once new endpoint is live
|
||||||
@ -144,11 +166,7 @@ export default function(app) {
|
|||||||
projectCompleted
|
projectCompleted
|
||||||
);
|
);
|
||||||
|
|
||||||
api.post(
|
api.post('/project-completed', send200toNonUser, projectCompleted);
|
||||||
'/project-completed',
|
|
||||||
send200toNonUser,
|
|
||||||
projectCompleted
|
|
||||||
);
|
|
||||||
|
|
||||||
api.post(
|
api.post(
|
||||||
'/backend-challenge-completed',
|
'/backend-challenge-completed',
|
||||||
@ -156,10 +174,7 @@ export default function(app) {
|
|||||||
backendChallengeCompleted
|
backendChallengeCompleted
|
||||||
);
|
);
|
||||||
|
|
||||||
router.get(
|
router.get('/challenges/current-challenge', redirectToCurrentChallenge);
|
||||||
'/challenges/current-challenge',
|
|
||||||
redirectToCurrentChallenge
|
|
||||||
);
|
|
||||||
|
|
||||||
router.get('/challenges', redirectToLearn);
|
router.get('/challenges', redirectToLearn);
|
||||||
|
|
||||||
@ -187,26 +202,22 @@ export default function(app) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const user = req.user;
|
const user = req.user;
|
||||||
return user.getCompletedChallenges$()
|
return user
|
||||||
|
.getCompletedChallenges$()
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
const completedDate = Date.now();
|
const completedDate = Date.now();
|
||||||
const {
|
const { id, files } = req.body;
|
||||||
id,
|
|
||||||
files
|
|
||||||
} = req.body;
|
|
||||||
|
|
||||||
const {
|
const { alreadyCompleted, updateData } = buildUserUpdate(user, id, {
|
||||||
alreadyCompleted,
|
|
||||||
updateData
|
|
||||||
} = buildUserUpdate(
|
|
||||||
user,
|
|
||||||
id,
|
id,
|
||||||
{ id, files, completedDate }
|
files,
|
||||||
);
|
completedDate
|
||||||
|
});
|
||||||
|
|
||||||
const points = alreadyCompleted ? user.points : user.points + 1;
|
const points = alreadyCompleted ? user.points : user.points + 1;
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user
|
||||||
|
.update$(updateData)
|
||||||
.doOnNext(() => user.manualReload())
|
.doOnNext(() => user.manualReload())
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.map(() => {
|
.map(() => {
|
||||||
@ -237,15 +248,13 @@ export default function(app) {
|
|||||||
return res.sendStatus(403);
|
return res.sendStatus(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
return req.user.getCompletedChallenges$()
|
return req.user
|
||||||
|
.getCompletedChallenges$()
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
const completedDate = Date.now();
|
const completedDate = Date.now();
|
||||||
const { id, solution, timezone, files } = req.body;
|
const { id, solution, timezone, files } = req.body;
|
||||||
|
|
||||||
const {
|
const { alreadyCompleted, updateData } = buildUserUpdate(
|
||||||
alreadyCompleted,
|
|
||||||
updateData
|
|
||||||
} = buildUserUpdate(
|
|
||||||
req.user,
|
req.user,
|
||||||
id,
|
id,
|
||||||
{ id, solution, completedDate, files },
|
{ id, solution, completedDate, files },
|
||||||
@ -255,7 +264,8 @@ export default function(app) {
|
|||||||
const user = req.user;
|
const user = req.user;
|
||||||
const points = alreadyCompleted ? user.points : user.points + 1;
|
const points = alreadyCompleted ? user.points : user.points + 1;
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user
|
||||||
|
.update$(updateData)
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.map(() => {
|
.map(() => {
|
||||||
if (type === 'json') {
|
if (type === 'json') {
|
||||||
@ -289,36 +299,38 @@ export default function(app) {
|
|||||||
|
|
||||||
const { user, body = {} } = req;
|
const { user, body = {} } = req;
|
||||||
|
|
||||||
const completedChallenge = _.pick(
|
const completedChallenge = _.pick(body, [
|
||||||
body,
|
'id',
|
||||||
[ 'id', 'solution', 'githubLink', 'challengeType', 'files' ]
|
'solution',
|
||||||
);
|
'githubLink',
|
||||||
|
'challengeType',
|
||||||
|
'files'
|
||||||
|
]);
|
||||||
completedChallenge.completedDate = Date.now();
|
completedChallenge.completedDate = Date.now();
|
||||||
|
|
||||||
if (
|
if (
|
||||||
!completedChallenge.solution ||
|
!completedChallenge.solution ||
|
||||||
// only basejumps require github links
|
// only basejumps require github links
|
||||||
(
|
(completedChallenge.challengeType === 4 && !completedChallenge.githubLink)
|
||||||
completedChallenge.challengeType === 4 &&
|
|
||||||
!completedChallenge.githubLink
|
|
||||||
)
|
|
||||||
) {
|
) {
|
||||||
req.flash(
|
req.flash(
|
||||||
'danger',
|
'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 res.sendStatus(403);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return user
|
||||||
return user.getCompletedChallenges$()
|
.getCompletedChallenges$()
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
const {
|
const { alreadyCompleted, updateData } = buildUserUpdate(
|
||||||
alreadyCompleted,
|
user,
|
||||||
updateData
|
completedChallenge.id,
|
||||||
} = buildUserUpdate(user, completedChallenge.id, completedChallenge);
|
completedChallenge
|
||||||
|
);
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user
|
||||||
|
.update$(updateData)
|
||||||
.doOnNext(() => user.manualReload())
|
.doOnNext(() => user.manualReload())
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.doOnNext(() => {
|
.doOnNext(() => {
|
||||||
@ -352,21 +364,20 @@ export default function(app) {
|
|||||||
|
|
||||||
const { user, body = {} } = req;
|
const { user, body = {} } = req;
|
||||||
|
|
||||||
const completedChallenge = _.pick(
|
const completedChallenge = _.pick(body, ['id', 'solution']);
|
||||||
body,
|
|
||||||
[ 'id', 'solution' ]
|
|
||||||
);
|
|
||||||
completedChallenge.completedDate = Date.now();
|
completedChallenge.completedDate = Date.now();
|
||||||
|
|
||||||
|
return user
|
||||||
return user.getCompletedChallenges$()
|
.getCompletedChallenges$()
|
||||||
.flatMap(() => {
|
.flatMap(() => {
|
||||||
const {
|
const { alreadyCompleted, updateData } = buildUserUpdate(
|
||||||
alreadyCompleted,
|
user,
|
||||||
updateData
|
completedChallenge.id,
|
||||||
} = buildUserUpdate(user, completedChallenge.id, completedChallenge);
|
completedChallenge
|
||||||
|
);
|
||||||
|
|
||||||
return user.update$(updateData)
|
return user
|
||||||
|
.update$(updateData)
|
||||||
.doOnNext(({ count }) => log('%s documents updated', count))
|
.doOnNext(({ count }) => log('%s documents updated', count))
|
||||||
.doOnNext(() => {
|
.doOnNext(() => {
|
||||||
if (type === 'json') {
|
if (type === 'json') {
|
||||||
@ -382,27 +393,22 @@ export default function(app) {
|
|||||||
.subscribe(() => {}, next);
|
.subscribe(() => {}, next);
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectToCurrentChallenge(req, res, next) {
|
async function redirectToCurrentChallenge(req, res, next) {
|
||||||
const { user } = req;
|
const { user } = req;
|
||||||
const challengeId = user && user.currentChallengeId;
|
const challengeId = user && user.currentChallengeId;
|
||||||
return getChallengeById(map, challengeId)
|
log(req.user.username);
|
||||||
.map(challenge => {
|
log(challengeId);
|
||||||
const { block, dashedName, superBlock } = challenge;
|
const challengeUrl = await challengeUrlResolver(challengeId).catch(next);
|
||||||
if (!dashedName || !block) {
|
log(challengeUrl);
|
||||||
|
if (challengeUrl === '/learn') {
|
||||||
// this should normally not be hit if database is properly seeded
|
// this should normally not be hit if database is properly seeded
|
||||||
throw new Error(dedent`
|
throw new Error(dedent`
|
||||||
Attempted to find '${dashedName}'
|
Attempted to find the url for ${challengeId}'
|
||||||
from '${ challengeId || 'no challenge id found'}'
|
|
||||||
but came up empty.
|
but came up empty.
|
||||||
db may not be properly seeded.
|
db may not be properly seeded.
|
||||||
`);
|
`);
|
||||||
}
|
}
|
||||||
return `${learnURL}/${dasherize(superBlock)}/${block}/${dashedName}`;
|
return res.redirect(`${homeLocation}${challengeUrl}`);
|
||||||
})
|
|
||||||
.subscribe(
|
|
||||||
redirect => res.redirect(redirect || learnURL),
|
|
||||||
next
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
function redirectToLearn(req, res) {
|
function redirectToLearn(req, res) {
|
||||||
@ -413,4 +419,5 @@ export default function(app) {
|
|||||||
}
|
}
|
||||||
return res.status(302).redirect(learnURL);
|
return res.status(302).redirect(learnURL);
|
||||||
}
|
}
|
||||||
|
done();
|
||||||
}
|
}
|
||||||
|
@ -4,6 +4,8 @@ const readDirP = require('readdirp-walk');
|
|||||||
|
|
||||||
const { parseMarkdown } = require('@freecodecamp/challenge-md-parser');
|
const { parseMarkdown } = require('@freecodecamp/challenge-md-parser');
|
||||||
|
|
||||||
|
const { dasherize } = require('./utils');
|
||||||
|
|
||||||
const challengesDir = path.resolve(__dirname, './challenges');
|
const challengesDir = path.resolve(__dirname, './challenges');
|
||||||
|
|
||||||
exports.getChallengesForLang = function getChallengesForLang(lang) {
|
exports.getChallengesForLang = function getChallengesForLang(lang) {
|
||||||
@ -57,6 +59,7 @@ async function buildCurriculum(file, curriculum) {
|
|||||||
);
|
);
|
||||||
const { name: blockName, order, superOrder } = meta;
|
const { name: blockName, order, superOrder } = meta;
|
||||||
challenge.block = blockName;
|
challenge.block = blockName;
|
||||||
|
challenge.dashedName = dasherize(challenge.title);
|
||||||
challenge.order = order;
|
challenge.order = order;
|
||||||
challenge.superOrder = superOrder;
|
challenge.superOrder = superOrder;
|
||||||
challenge.superBlock = superBlock;
|
challenge.superBlock = superBlock;
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
const path = require('path');
|
const path = require('path');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
|
require('dotenv').config({ path: path.resolve(__dirname, '../../../.env') });
|
||||||
const MongoClient = require('mongodb').MongoClient;
|
const { MongoClient, ObjectID } = require('mongodb');
|
||||||
const { getChallengesForLang } = require('@freecodecamp/curriculum');
|
const { getChallengesForLang } = require('@freecodecamp/curriculum');
|
||||||
const { flatten } = require('lodash');
|
const { flatten } = require('lodash');
|
||||||
const debug = require('debug');
|
const debug = require('debug');
|
||||||
@ -9,7 +9,7 @@ const debug = require('debug');
|
|||||||
const { createPathMigrationMap } = require('./createPathMigrationMap');
|
const { createPathMigrationMap } = require('./createPathMigrationMap');
|
||||||
|
|
||||||
const log = debug('fcc:tools:seedChallenges');
|
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) {
|
function handleError(err, client) {
|
||||||
if (err) {
|
if (err) {
|
||||||
@ -32,14 +32,16 @@ MongoClient.connect(
|
|||||||
function(err, client) {
|
function(err, client) {
|
||||||
handleError(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 db = client.db('freecodecamp');
|
||||||
const challenges = db.collection('challenge');
|
const challengeCollection = db.collection('challenge');
|
||||||
|
|
||||||
challenges.deleteMany({}, err => {
|
challengeCollection.deleteMany({}, err => {
|
||||||
handleError(err, client);
|
handleError(err, client);
|
||||||
|
|
||||||
|
log('deleted all the challenges');
|
||||||
|
|
||||||
const curriculum = getChallengesForLang(lang);
|
const curriculum = getChallengesForLang(lang);
|
||||||
|
|
||||||
const allChallenges = Object.keys(curriculum)
|
const allChallenges = Object.keys(curriculum)
|
||||||
@ -51,18 +53,25 @@ MongoClient.connect(
|
|||||||
return [...challengeArray, ...flatten(challengesForBlock)];
|
return [...challengeArray, ...flatten(challengesForBlock)];
|
||||||
}, [])
|
}, [])
|
||||||
.map(challenge => {
|
.map(challenge => {
|
||||||
challenge._id = challenge.id.slice(0);
|
const currentId = challenge.id.slice(0);
|
||||||
|
challenge._id = ObjectID(currentId);
|
||||||
delete challenge.id;
|
delete challenge.id;
|
||||||
return challenge;
|
return challenge;
|
||||||
});
|
});
|
||||||
|
|
||||||
try {
|
try {
|
||||||
challenges.insertMany(allChallenges, { ordered: false });
|
challengeCollection.insertMany(
|
||||||
|
allChallenges,
|
||||||
|
{ ordered: false },
|
||||||
|
err => {
|
||||||
|
handleError(err, client);
|
||||||
|
log('challenge seed complete');
|
||||||
|
client.close();
|
||||||
|
}
|
||||||
|
);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
handleError(e, client);
|
handleError(e, client);
|
||||||
} finally {
|
} finally {
|
||||||
log('challenge seed complete');
|
|
||||||
client.close();
|
|
||||||
log('generating path migration map');
|
log('generating path migration map');
|
||||||
const pathMap = createPathMigrationMap(curriculum);
|
const pathMap = createPathMigrationMap(curriculum);
|
||||||
const outputDir = path.resolve(
|
const outputDir = path.resolve(
|
||||||
|
Reference in New Issue
Block a user