From 766b6f98f15fa04aa22b49b1bcbd635a30e7170c Mon Sep 17 00:00:00 2001 From: Stuart Taylor Date: Fri, 23 Mar 2018 14:35:29 +0000 Subject: [PATCH] feat(schema): Implement challenge schema --- package-lock.json | 57 +++++++++++++++++++++ package.json | 2 + seed/index.js | 33 ++++++++++-- seed/schema/challengeSchema.js | 93 ++++++++++++++++++++++++++++++++++ 4 files changed, 181 insertions(+), 4 deletions(-) create mode 100644 seed/schema/challengeSchema.js diff --git a/package-lock.json b/package-lock.json index 90eb49be67f..71b9cac704d 100644 --- a/package-lock.json +++ b/package-lock.json @@ -9130,6 +9130,46 @@ "resolved": "https://registry.npmjs.org/jmespath/-/jmespath-0.15.0.tgz", "integrity": "sha1-o/Iiqarp+Wb10nx5ZRDigJF2Qhc=" }, + "joi": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/joi/-/joi-13.1.2.tgz", + "integrity": "sha512-bZZSQYW5lPXenOfENvgCBPb9+H6E6MeNWcMtikI04fKphj5tvFL9TOb+H2apJzbCrRw/jebjTH8z6IHLpBytGg==", + "dev": true, + "requires": { + "hoek": "5.0.3", + "isemail": "3.1.1", + "topo": "3.0.0" + }, + "dependencies": { + "hoek": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz", + "integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw==", + "dev": true + }, + "isemail": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/isemail/-/isemail-3.1.1.tgz", + "integrity": "sha512-mVjAjvdPkpwXW61agT2E9AkGoegZO7SdJGCezWwxnETL58f5KwJ4vSVAMBUL5idL6rTlYAIGkX3n4suiviMLNw==", + "dev": true, + "requires": { + "punycode": "2.1.0" + } + }, + "punycode": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.0.tgz", + "integrity": "sha1-X4Y+3Im5bbCQdLrXlHvwkFbKTn0=", + "dev": true + } + } + }, + "joi-objectid": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/joi-objectid/-/joi-objectid-2.0.0.tgz", + "integrity": "sha1-VlSVc6Zrp5Xc9rniJt5fOy027Do=", + "dev": true + }, "jquery": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/jquery/-/jquery-3.1.1.tgz", @@ -17448,6 +17488,23 @@ "resolved": "https://registry.npmjs.org/toml/-/toml-2.3.3.tgz", "integrity": "sha512-O7L5hhSQHxuufWUdcTRPfuTh3phKfAZ/dqfxZFoxPCj2RYmpaSGLEIs016FCXItQwNr08yefUB5TSjzRYnajTA==" }, + "topo": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/topo/-/topo-3.0.0.tgz", + "integrity": "sha512-Tlu1fGlR90iCdIPURqPiufqAlCZYzLjHYVVbcFWDMcX7+tK8hdZWAfsMrD/pBul9jqHHwFjNdf1WaxA9vTRRhw==", + "dev": true, + "requires": { + "hoek": "5.0.3" + }, + "dependencies": { + "hoek": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/hoek/-/hoek-5.0.3.tgz", + "integrity": "sha512-Bmr56pxML1c9kU+NS51SMFkiVQAb+9uFfXwyqR2tn4w2FPvmPt65eZ9aCcEfRXd9G74HkZnILC6p967pED4aiw==", + "dev": true + } + } + }, "toposort": { "version": "0.2.12", "resolved": "https://registry.npmjs.org/toposort/-/toposort-0.2.12.tgz", diff --git a/package.json b/package.json index b428c4a831a..371497d0091 100644 --- a/package.json +++ b/package.json @@ -176,6 +176,8 @@ "gulp-util": "^3.0.6", "husky": "^0.14.3", "istanbul-coveralls": "^1.0.3", + "joi": "^13.1.2", + "joi-objectid": "^2.0.0", "jsdom": "^11.5.1", "json-loader": "~0.5.2", "less": "^2.5.1", diff --git a/seed/index.js b/seed/index.js index 3932935d794..d324da151f8 100644 --- a/seed/index.js +++ b/seed/index.js @@ -9,6 +9,7 @@ const utils = require('../server/utils'); const getChallenges = require('./getChallenges'); const app = require('../server/server'); const createDebugger = require('debug'); +const { validateChallenge } = require('./schema/challengeSchema'); const log = createDebugger('fcc:seed'); // force logger to always output @@ -108,7 +109,7 @@ Observable.combineLatest( challenge.order = order; challenge.suborder = index + 1; challenge.block = dasherize(blockName); - challenge.blockId = block.id; + challenge.blockId = '' + block.id; challenge.isBeta = challenge.isBeta || isBeta; challenge.isComingSoon = challenge.isComingSoon || isComingSoon; challenge.isLocked = challenge.isLocked || isLocked; @@ -123,11 +124,35 @@ Observable.combineLatest( .join(' '); challenge.required = (challenge.required || []).concat(required); challenge.template = challenge.template || template; - - return challenge; + return _.omit( + challenge, + [ + 'betaSolutions', + 'betaTests', + 'hints', + 'MDNlinks', + 'null', + 'rawSolutions', + 'react', + 'reactRedux', + 'redux', + 'releasedOn', + 'translations', + 'type' + ] + ); }); }) - .flatMap(challenges => createChallenges(challenges)); + .flatMap(challenges => { + challenges.forEach(challenge => { + const result = validateChallenge(challenge); + if (result.error) { + console.log(result.value); + throw new Error(result.error); + } + }); + return createChallenges(challenges); + }); }) .subscribe( function(challenges) { diff --git a/seed/schema/challengeSchema.js b/seed/schema/challengeSchema.js new file mode 100644 index 00000000000..8158d2c3ae7 --- /dev/null +++ b/seed/schema/challengeSchema.js @@ -0,0 +1,93 @@ +const Joi = require('joi'); +Joi.objectId = require('joi-objectid')(Joi); + +const schema = Joi.object().keys({ + block: Joi.string(), + blockId: Joi.objectId(), + challengeSeed: Joi.array().items( + Joi.string().allow('') + ), + challengeType: Joi.number().min(0).max(9).required(), + checksum: Joi.number(), + dashedName: Joi.string(), + description: Joi.array().items( + + // classic/modern challenges + Joi.string().allow(''), + + // step challenges + Joi.array().items( + Joi.string().allow('') + ).length(4), + + // quiz challenges + Joi.object().keys({ + subtitle: Joi.string(), + question: Joi.string(), + choices: Joi.array(), + answer: Joi.number(), + explanation: Joi.string() + }) + + ).required(), + fileName: Joi.string(), + files: Joi.object().pattern( + /(jsx?|html|css|sass)$/, + Joi.object().keys({ + key: Joi.string(), + ext: Joi.string(), + name: Joi.string(), + head: Joi.string().allow(''), + tail: Joi.string().allow(''), + contents: Joi.string() + }) + ), + guideUrl: Joi.string().uri({ scheme: 'https' }), + head: Joi.array().items( + Joi.string().allow('') + ), + helpRoom: Joi.string(), + id: Joi.objectId().required(), + isBeta: Joi.bool(), + isComingSoon: Joi.bool(), + isLocked: Joi.bool(), + isPrivate: Joi.bool(), + isRequired: Joi.bool(), + name: Joi.string(), + order: Joi.number(), + required: Joi.array().items( + Joi.object().keys({ + link: Joi.string(), + raw: Joi.bool(), + src: Joi.string(), + crossDomain: Joi.bool() + }) + ), + solutions: Joi.array().items( + Joi.string().optional() + ), + superBlock: Joi.string(), + superOrder: Joi.number(), + suborder: Joi.number(), + tail: Joi.array().items( + Joi.string().allow('') + ), + tests: Joi.array().items( + Joi.string().min(2), + Joi.object().keys({ + text: Joi.string().required(), + testString: Joi.string().allow('').required() + }), + Joi.object().keys({ + id: Joi.objectId().required(), + title: Joi.string().required() + }) + ), + template: Joi.string(), + time: Joi.string().allow(''), + title: Joi.string().required() +}); + +exports.validateChallenge = function validateChallenge(challenge) { + return Joi.validate(challenge, schema); +};