committed by
Mrugesh Mohapatra
parent
4b7dc90763
commit
5f0068e9c8
@ -5,20 +5,26 @@ const { dasherize } = require('./utils');
|
|||||||
const { viewTypes } = require('./utils/challengeTypes');
|
const { viewTypes } = require('./utils/challengeTypes');
|
||||||
const { blockNameify } = require('./utils/blockNameify');
|
const { blockNameify } = require('./utils/blockNameify');
|
||||||
|
|
||||||
|
const backend = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'./src/templates/Challenges/backend/Show.js'
|
||||||
|
);
|
||||||
const classic = path.resolve(
|
const classic = path.resolve(
|
||||||
__dirname,
|
__dirname,
|
||||||
'./src/templates/Challenges/classic/Show.js'
|
'./src/templates/Challenges/classic/Show.js'
|
||||||
);
|
);
|
||||||
const intro = path.resolve(__dirname, './src/templates/Introduction/Intro.js');
|
const intro = path.resolve(__dirname, './src/templates/Introduction/Intro.js');
|
||||||
|
const project = path.resolve(
|
||||||
|
__dirname,
|
||||||
|
'./src/templates/Challenges/project/Show.js'
|
||||||
|
);
|
||||||
|
|
||||||
const views = {
|
const views = {
|
||||||
// backend: BackEnd,
|
backend,
|
||||||
classic,
|
classic,
|
||||||
modern: classic,
|
modern: classic,
|
||||||
project: path.resolve(__dirname, './src/templates/Challenges/project/Show.js')
|
project
|
||||||
// quiz: Quiz,
|
// quiz: Quiz
|
||||||
// simple: Project,
|
|
||||||
// invalid: NotFound
|
|
||||||
};
|
};
|
||||||
|
|
||||||
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
||||||
@ -31,16 +37,13 @@ exports.onCreateNode = function onCreateNode({ node, boundActionCreators }) {
|
|||||||
)}`;
|
)}`;
|
||||||
createNodeField({ node, name: 'slug', value: slug });
|
createNodeField({ node, name: 'slug', value: slug });
|
||||||
createNodeField({ node, name: 'blockName', value: blockNameify(block) });
|
createNodeField({ node, name: 'blockName', value: blockNameify(block) });
|
||||||
// TODO: Normalise tests to { test: '', testString: ''}?
|
|
||||||
createNodeField({ node, name: 'tests', value: tests });
|
createNodeField({ node, name: 'tests', value: tests });
|
||||||
}
|
}
|
||||||
|
|
||||||
if (node.internal.type === 'MarkdownRemark') {
|
if (node.internal.type === 'MarkdownRemark') {
|
||||||
const { frontmatter: { block, superBlock, title } } = node;
|
const { frontmatter: { block, superBlock } } = node;
|
||||||
|
|
||||||
const slug = `/${dasherize(superBlock)}${
|
const slug = `/${dasherize(superBlock)}/${dasherize(block)}`;
|
||||||
block ? `/${dasherize(block)}${title ? `/${dasherize(title)}` : ''}` : ''
|
|
||||||
}`;
|
|
||||||
|
|
||||||
createNodeField({ node, name: 'slug', value: slug });
|
createNodeField({ node, name: 'slug', value: slug });
|
||||||
}
|
}
|
||||||
@ -102,6 +105,9 @@ exports.createPages = ({ graphql, boundActionCreators }) => {
|
|||||||
challengeType,
|
challengeType,
|
||||||
id
|
id
|
||||||
} = edge.node;
|
} = edge.node;
|
||||||
|
if (challengeType === 7) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
const next = thisArray[index + 1];
|
const next = thisArray[index + 1];
|
||||||
const nextChallengePath = next ? next.node.fields.slug : '/';
|
const nextChallengePath = next ? next.node.fields.slug : '/';
|
||||||
createPage({
|
createPage({
|
||||||
|
@ -2,7 +2,7 @@ const crypto = require('crypto');
|
|||||||
|
|
||||||
function createChallengeNodes(challenge, reporter) {
|
function createChallengeNodes(challenge, reporter) {
|
||||||
if (typeof challenge.description[0] !== 'string') {
|
if (typeof challenge.description[0] !== 'string') {
|
||||||
reporter.panic(`
|
reporter.warn(`
|
||||||
|
|
||||||
${challenge.title} has a description that will break things!
|
${challenge.title} has a description that will break things!
|
||||||
|
|
||||||
|
@ -45,6 +45,7 @@ that delivers challenge files to the plugin
|
|||||||
const { source } = pluginOptions;
|
const { source } = pluginOptions;
|
||||||
const createAndProcessNodes = () =>
|
const createAndProcessNodes = () =>
|
||||||
source()
|
source()
|
||||||
|
.filter(node => node.challengeType !== 7)
|
||||||
.map(nodes => nodes.map(node => createChallengeNodes(node, reporter)))
|
.map(nodes => nodes.map(node => createChallengeNodes(node, reporter)))
|
||||||
.map(nodes => nodes.map(node => createNode(node)))
|
.map(nodes => nodes.map(node => createNode(node)))
|
||||||
.subscribe();
|
.subscribe();
|
||||||
|
@ -0,0 +1,174 @@
|
|||||||
|
{
|
||||||
|
"name": "API and Microservice Projects",
|
||||||
|
"order": 4,
|
||||||
|
"time": "150 hours",
|
||||||
|
"helpRoom": "HelpBackend",
|
||||||
|
"challenges": [
|
||||||
|
{
|
||||||
|
"id": "bd7158d8c443edefaeb5bdef",
|
||||||
|
"title": "Timestamp Microservice",
|
||||||
|
"description": [
|
||||||
|
"Build a full stack JavaScript app that is functionally similar to this: <a href='https://curse-arrow.glitch.me/' target='_blank'>https://curse-arrow.glitch.me/</a>.",
|
||||||
|
"Working on this project will involve you writing your code on Glitch on our starter project. After completing this project you can copy your public glitch url (to the homepage of your app) into this screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.",
|
||||||
|
"Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-project-timestamp/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-project-timestamp/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "I can pass a string as a parameter, and it will check to see whether that string contains either a unix timestamp or a natural language date (example: January 1, 2016).",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "If it does, it returns both the Unix timestamp and the natural language form of that date.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "If it does not contain a date or Unix timestamp, it returns null for those properties.",
|
||||||
|
"testString": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"isRequired": true,
|
||||||
|
"releasedOn": "January 1, 2016",
|
||||||
|
"translations": {
|
||||||
|
"es": {
|
||||||
|
"title": "Microservicio de Marca Temporal"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bd7158d8c443edefaeb5bdff",
|
||||||
|
"title": "Request Header Parser Microservice",
|
||||||
|
"description": [
|
||||||
|
"Build a full stack JavaScript app that is functionally similar to this: <a href='https://dandelion-roar.glitch.me/' target='_blank'>https://dandelion-roar.glitch.me/</a>.",
|
||||||
|
"Working on this project will involve you writing your code on Glitch on our starter project. After completing this project you can copy your public glitch url (to the homepage of your app) into this screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.",
|
||||||
|
"Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-project-headerparser/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-project-headerparser/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "I can get the IP address, language and operating system for my browser.",
|
||||||
|
"testString": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"isRequired": true,
|
||||||
|
"releasedOn": "January 1, 2016",
|
||||||
|
"translations": {
|
||||||
|
"es": {
|
||||||
|
"title": "Microservicio para analizar el encabezado de una petición"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bd7158d8c443edefaeb5bd0e",
|
||||||
|
"title": "URL Shortener Microservice",
|
||||||
|
"description": [
|
||||||
|
"Build a full stack JavaScript app that is functionally similar to this: <a href='https://thread-paper.glitch.me/' target='_blank'>https://thread-paper.glitch.me/</a>.",
|
||||||
|
"Working on this project will involve you writing your code on Glitch on our starter project. After completing this project you can copy your public glitch url (to the homepage of your app) into this screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.",
|
||||||
|
"Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-project-urlshortener/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-project-urlshortener/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "I can pass a URL as a parameter and I will receive a shortened URL in the JSON response.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "If I pass an invalid URL that doesn't follow the valid http://www.example.com format, the JSON response will contain an error instead.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "When I visit that shortened URL, it will redirect me to my original link.",
|
||||||
|
"testString": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"isRequired": true,
|
||||||
|
"releasedOn": "January 1, 2016",
|
||||||
|
"translations": {
|
||||||
|
"es": {
|
||||||
|
"title": "Microservicio para acortar URLs"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "5a8b073d06fa14fcfde687aa",
|
||||||
|
"title": "Exercise Tracker",
|
||||||
|
"description": [
|
||||||
|
"Build a full stack JavaScript app that is functionally similar to this: <a href='https://fuschia-custard.glitch.me/' target='_blank'>https://fuschia-custard.glitch.me/</a>.",
|
||||||
|
"Working on this project will involve you writing your code on Glitch on our starter project. After completing this project you can copy your public glitch url (to the homepage of your app) into this screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.",
|
||||||
|
"Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-project-exercisetracker/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-project-exercisetracker/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "I can create a user by posting form data username to /api/exercise/new-user and returned will be an object with username and <code>_id</code>.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "I can get an array of all users by getting api/exercise/users with the same info as when creating a user.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "I can add an exercise to any user by posting form data userId(_id), description, duration, and optionally date to /api/exercise/add. If no date supplied it will use current date. App will return the user object with the exercise fields added.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "I can retrieve a full exercise log of any user by getting /api/exercise/log with a parameter of userId(_id). App will return the user object with added array log and count (total exercise count).",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "I can retrieve part of the log of any user by also passing along optional parameters of from & to or limit. (Date format yyyy-mm-dd, limit = int)",
|
||||||
|
"testString": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"isRequired": true,
|
||||||
|
"releasedOn": "February 17, 2017",
|
||||||
|
"translations": {
|
||||||
|
"es": {
|
||||||
|
"title": "Capa de abstracción para buscar imágenes"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "bd7158d8c443edefaeb5bd0f",
|
||||||
|
"title": "File Metadata Microservice",
|
||||||
|
"description": [
|
||||||
|
"Build a full stack JavaScript app that is functionally similar to this: <a href='https://purple-paladin.glitch.me/' target='_blank'>https://purple-paladin.glitch.me/</a>.",
|
||||||
|
"Working on this project will involve you writing your code on Glitch on our starter project. After completing this project you can copy your public glitch url (to the homepage of your app) into this screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.",
|
||||||
|
"Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-project-filemetadata/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-project-filemetadata/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "I can submit a FormData object that includes a file upload.",
|
||||||
|
"testString": ""
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "When I submit something, I will receive the file size in bytes within the JSON response.",
|
||||||
|
"testString": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"isRequired": true,
|
||||||
|
"releasedOn": "January 1, 2016",
|
||||||
|
"translations": {
|
||||||
|
"es": {
|
||||||
|
"title": "Microservicio de metadatos de archivos"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,287 @@
|
|||||||
|
{
|
||||||
|
"name": "Basic Node and Express",
|
||||||
|
"order": 2,
|
||||||
|
"time": "5 hours",
|
||||||
|
"helpRoom": "Help",
|
||||||
|
"challenges": [
|
||||||
|
{
|
||||||
|
"id": "587d7fb0367417b2b2512bed",
|
||||||
|
"title": "Meet the Node console",
|
||||||
|
"description": [
|
||||||
|
"During the development process, it is important to be able to check what’s going on in your code. Node is just a JavaScript environment. Like client side JavaScript, you can use the console to display useful debug information. On your local machine, you would see the console output in a terminal. On Glitch you can open the logs in the lower part of the screen. You can toggle the log panel with the button ‘Logs’ (top-left, under the app name).",
|
||||||
|
"To get started, just print the classic \"Hello World\" in the console. We recommend to keep the log panel open while working at these challenges. Reading the logs you can be aware of the nature of the errors that may occur."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "<code>\"Hello World\"</code> should be in the console",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/hello-console').then(data => { assert.isTrue(data.passed, '\"Hello World\" is not in the server console'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb0367417b2b2512bee",
|
||||||
|
"title": "Start a Working Express Server",
|
||||||
|
"description": [
|
||||||
|
"In the first two lines of the file myApp.js you can see how it’s easy to create an Express app object. This object has several methods, and we will learn many of them in these challenges. One fundamental method is <code>app.listen(port)</code>. It tells your server to listen on a given port, putting it in running state. You can see it at the bottom of the file. It is inside comments because for testing reasons we need the app to be running in background. All the code that you may want to add goes between these two fundamental parts. Glitch stores the port number in the environemet variable <code>process.env.PORT</code>. Its value is <code>3000</code>.",
|
||||||
|
"Let’s serve our first string! In Express, routes takes the following structure: <code>app.METHOD(PATH, HANDLER)</code>. METHOD is an http method in lowercase. PATH is a relative path on the server (it can be a string, or even a regular expression). HANDLER is a function that Express calls when the route is matched.",
|
||||||
|
"Handlers take the form <code>function(req, res) {...}</code>, where req is the request object, and res is the response object. For example, the handler",
|
||||||
|
"<blockquote>function(req, res) {<br> res.send('Response String');<br>}</blockquote>",
|
||||||
|
"will serve the string 'Response String'.",
|
||||||
|
"Use the <code>app.get()</code> method to serve the string Hello Express, to GET requests matching the / root path. Be sure that your code works by looking at the logs, then see the results in your browser, clicking the button ‘Show Live’ in the Glitch UI."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Your app should serve the string 'Hello Express'",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url')).then(data => { assert.equal(data, 'Hello Express', 'Your app does not serve the text \"Hello Express\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb0367417b2b2512bef",
|
||||||
|
"title": "Serve an HTML File",
|
||||||
|
"description": [
|
||||||
|
"We can respond with a file using the method <code>res.sendFile(path)</code>.",
|
||||||
|
"You can put it inside the <code>app.get('/', ...)</code> route handler. Behind the scenes this method will set the appropriate headers to instruct your browser on how to handle the file you want to send, according to its type. Then it will read and send the file. This method needs an absolute file path. We recommend you to use the Node global variable <code>__dirname</code> to calculate the path.",
|
||||||
|
"e.g. <code>absolutePath = __dirname + relativePath/file.ext</code>.",
|
||||||
|
"The file to send is <code>/views/index.html</code>. Try to ‘Show Live’ your app, you should see a big HTML heading (and a form that we will use later…), with no style applied.",
|
||||||
|
"Note: You can edit the solution of the previous challenge, or create a new one. If you create a new solution, keep in mind that Express evaluates the routes from top to bottom. It executes the handler for the first match. You have to comment out the preceding solution, or the server will keep responding with a string."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Your app should serve the file views/index.html",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url')).then(data => { assert.match(data, /<h1>.*<\\/h1>/, 'Your app does not serve the expected HTML'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb0367417b2b2512bf0",
|
||||||
|
"title": "Serve Static Assets",
|
||||||
|
"description": [
|
||||||
|
"An HTML server usually has one or more directories that are accessible by the user. You can place there the static assets needed by your application (stylesheets, scripts, images). In Express you can put in place this functionality using the middleware <code>express.static(path)</code>, where the parameter is the absolute path of the folder containing the assets. If don’t know what a middleware is, don’t worry. We’ll discuss about it later in details. Basically middlewares are functions that intercept route handlers, adding some kind of information. A middleware needs to be mounted using the method <code>app.use(path, middlewareFunction)</code>. The first path argument is optional. If you don’t pass it, the middleware will be executed for all the requests.",
|
||||||
|
"Mount the <code>express.static()</code> middleware for all the requests with <code>app.use()</code>. The absolute path to the assets folder is <code>__dirname + /public</code>.",
|
||||||
|
"Now your app should be able to serve a CSS stylesheet. From outside the public folder will appear mounted to the root directory. Your front-page should look a little better now!"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Your app should serve asset files from the <code>/public</code> directory",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/style.css').then(data => { assert.match(data, /body\\s*\\{[^\\}]*\\}/, 'Your app does not serve static assets'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb1367417b2b2512bf1",
|
||||||
|
"title": "Serve JSON on a Specific Route",
|
||||||
|
"description": [
|
||||||
|
"While an HTML server serves (you guessed it!) HTML, an API serves data. A <dfn>REST</dfn> (REpresentational State Transfer) API allows data exchange in a simple way, without the need for clients to know any detail about the server. The client only needs to know where the resource is (the URL), and the action it wants to perform on it (the verb). The GET verb is used when you are fetching some information, without modifying anything. These days, the preferred data format for moving information around the web is JSON. Simply put, JSON is a convenient way to represent a JavaScript object as a string, so it can be easily transmitted.",
|
||||||
|
"Let's create a simple API by creating a route that responds with JSON at the path <code>/json</code>. You can do it as usual, with the <code>app.get()</code> method. Inside the route handler use the method <code>res.json()</code>, passing in an object as an argument. This method closes the request-response loop, returning the data. Behind the scenes it converts a valid JavaScript object into a string, then sets the appropriate headers to tell your browser that you are serving JSON, and sends the data back. A valid object has the usual structure <code>{key: data}</code>. Data can ba a number, a string, a nested object or an array. Data can also be a variable or the result of a function call; in which case it will be evaluated before being converted into a string.",
|
||||||
|
"Serve the object <code>{\"message\": \"Hello json\"}</code> as a response in JSON format, to the GET requests to the route <code>/json</code>. Then point your browser to your-app-url/json, you should see the message on the screen."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "The endpoint <code>/json</code> should serve the json object <code>{\"message\": \"Hello json\"}</code>",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/json').then(data => { assert.equal(data.message, 'Hello json', 'The \\'/json\\' endpoint does not serve the right data'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb1367417b2b2512bf2",
|
||||||
|
"title": "Use the .env File",
|
||||||
|
"description": [
|
||||||
|
"The <code>.env</code> file is a hidden file that is used to pass environment variables to your application. This file is secret, no one but you can access it, and it can be used to store data that you want to keep private or hidden. For example, you can store API keys from external services or your database URI. You can also use it to store configuration options. By setting configuration options, you can change the behavior of your application, without the need to rewrite some code.",
|
||||||
|
"The environment variables are accessible from the app as <code>process.env.VAR_NAME</code>. The <code>process.env</code> object is a global Node object, and variables are passed as strings. By convention, the variable names are all uppercase, with words separated by an underscore. The <code>.env</code> is a shell file, so you don’t need to wrap names or values in quotes. It is also important to note that there cannot be space around the equals sign when you are assigning values to your variables, e.g. <code>VAR_NAME=value</code>. Usually, you will put each variable definition on a separate line.",
|
||||||
|
"Let's add an environment variable as a configuration option. Store the variable <code>MESSAGE_STYLE=uppercase</code> in the <code>.env</code> file. Then tell the GET <code>/json</code> route handler that you created in the last challenge to transform the response object’s message to uppercase if <code>process.env.MESSAGE_STYLE</code> equals <code>uppercase</code>. The response object should become <code>{\"message\": \"HELLO JSON\"}</code>."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "The response of the endpoint <code>/json</code> should change according to the environment variable <code>MESSAGE_STYLE</code>",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/use-env-vars').then(data => { assert.isTrue(data.passed, 'The response of \"/json\" does not change according to MESSAGE_STYLE'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb1367417b2b2512bf3",
|
||||||
|
"title": "Implement a Root-Level Request Logger Middleware",
|
||||||
|
"description": [
|
||||||
|
"Before we introduced the <code>express.static()</code> middleware function. Now it’s time to see what middleware is, in more detail. Middleware functions are functions that take 3 arguments: the request object, the response object, and the next function in the application’s request-response cycle. These functions execute some code that can have side effects on the app, and usually add informations to the request or response objects. They can also end the cycle sending the response, when some condition is met. If they don’t send the response, when they are done they start the execution of the next function in the stack. This is triggered calling the 3rd argument <code>next()</code>. More information in the <a href='http://expressjs.com/en/guide/using-middleware.html' target='_blank'>express documentation</a>.",
|
||||||
|
"Look at the following example :",
|
||||||
|
"<blockquote>function(req, res, next) {<br> console.log(\"I'm a middleware...\");<br> next();<br>}</blockquote>",
|
||||||
|
"Let’s suppose we mounted this function on a route. When a request matches the route, it displays the string “I’m a middleware…”. Then it executes the next function in the stack.",
|
||||||
|
"In this exercise we are going to build a root-level middleware. As we have seen in challenge 4, to mount a middleware function at root level we can use the method <code>app.use(<mware-function>)</code>. In this case the function will be executed for all the requests, but you can also set more specific conditions. For example, if you want a function to be executed only for POST requests, you could use <code>app.post(<mware-function>)</code>. Analogous methods exist for all the http verbs (GET, DELETE, PUT, …).",
|
||||||
|
"Build a simple logger. For every request, it should log in the console a string taking the following format: <code>method path - ip</code>. An example would look like: <code>GET /json - ::ffff:127.0.0.1</code>. Note that there is a space between <code>method</code> and <code>path</code> and that the dash separating <code>path</code> and <code>ip</code> is surrounded by a space on either side. You can get the request method (http verb), the relative route path, and the caller’s ip from the request object, using <code>req.method</code>, <code>req.path</code> and <code>req.ip</code>. Remember to call <code>next()</code> when you are done, or your server will be stuck forever. Be sure to have the ‘Logs’ opened, and see what happens when some request arrives…",
|
||||||
|
"Hint: Express evaluates functions in the order they appear in the code. This is true for middleware too. If you want it to work for all the routes, it should be mounted before them."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Root level logger middleware should be active",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/root-middleware-logger').then(data => { assert.isTrue(data.passed, 'root-level logger is not working as expected'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb1367417b2b2512bf4",
|
||||||
|
"title": "Chain Middleware to Create a Time Server",
|
||||||
|
"description": [
|
||||||
|
"Middleware can be mounted at a specific route using <code>app.METHOD(path, middlewareFunction)</code>. Middleware can also be chained inside route definition.",
|
||||||
|
"Look at the following example:",
|
||||||
|
"<blockquote>app.get('/user', function(req, res, next) {<br> req.user = getTheUserSync(); // Hypotetical synchronous operation<br> next();<br>}, function(req, res) {<br> res.send(req.user);<br>})</blockquote>",
|
||||||
|
"This approach is useful to split the server operations into smaller units. That leads a to a better app structure, and the possibility to reuse code in different places. This approach can also be used to perform some validation on the data. At each point of the middleware stack you can block the execution of the current chain and pass control to functions specifically designed to handle errors. Or you can pass control to the next matching route, to handle special cases. We will see how in the advanced Express section.",
|
||||||
|
"In the route <code>app.get('/now', ...)</code> chain a middleware function and the final handler. In the middleware function you should add the current time to the request object in the <code>req.time</code> key. You can use <code>new Date().toString()</code>. In the handler, respond with a JSON object, taking the structure <code>{time: req.time}</code>.",
|
||||||
|
"Hint: The test will not pass if you don’t chain the middleware. If you mount the function somewhere else, the test will fail, even if the output result is correct."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "The /now endpoint should have mounted middleware",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/chain-middleware-time').then(data => { assert.equal(data.stackLength, 2, '\"/now\" route has no mounted middleware'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "The /now endpoint should return a time that is +/- 20 secs from now",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/chain-middleware-time').then(data => { var now = new Date(); assert.isAtMost(Math.abs(new Date(data.time) - now), 20000, 'the returned time is not between +- 20 secs from now'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb2367417b2b2512bf5",
|
||||||
|
"title": "Get Route Parameter Input from the Client",
|
||||||
|
"description": [
|
||||||
|
"When building an API, we have to allow users to comunicate us what they want to get from our service. For example, if the client is requesting information about a user stored in the database, they need a way to let us know which user they're interested in. One possible way to achieve this result is using route parameters. Route parameters are named segments of the URL, delimited by slashes (/). Each segment captures the value of the part of the URL which matches its position. The captured values can be found in the <code>req.params</code> object.",
|
||||||
|
"<blockquote>route_path: '/user/:userId/book/:bookId'<br>actual_request_URL: '/user/546/book/6754' <br>req.params: {userId: '546', bookId: '6754'}</blockquote>",
|
||||||
|
"Build an echo server, mounted at the route <code>GET /:word/echo</code>. Respond with a JSON object, taking the structure <code>{echo: word}</code>. You can find the word to be repeated at <code>req.params.word</code>. You can test your route from your browser's address bar, visiting some matching routes, e.g. your-app-rootpath/freecodecamp/echo"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Test 1 : Your echo server should repeat words correctly",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/eChOtEsT/echo').then(data => { assert.equal(data.echo, 'eChOtEsT', 'Test 1: the echo server is not working as expected') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Test 2 : Your echo server should repeat words correctly",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/ech0-t3st/echo').then(data => { assert.equal(data.echo, 'ech0-t3st', 'Test 2: the echo server is not working as expected') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb2367417b2b2512bf6",
|
||||||
|
"title": "Get Query Parameter Input from the Client",
|
||||||
|
"description": [
|
||||||
|
"Another common way to get input from the client is by encoding the data after the route path, using a query string. The query string is delimited by a question mark (?), and includes field=value couples. Each couple is separated by an ampersand (&). Express can parse the data from the query string, and populate the object <code>req.query</code>. Some characters cannot be in URLs, they have to be encoded in a <a href='https://en.wikipedia.org/wiki/Percent-encoding' target='_blank'>different format</a> before you can send them. If you use the API from JavaScript, you can use specific methods to encode/decode these characters.",
|
||||||
|
"<blockquote>route_path: '/library'<br>actual_request_URL: '/library?userId=546&bookId=6754' <br>req.query: {userId: '546', bookId: '6754'}</blockquote>",
|
||||||
|
"Build an API endpoint, mounted at <code>GET /name</code>. Respond with a JSON document, taking the structure <code>{ name: 'firstname lastname'}</code>. The first and last name parameters should be encoded in a query string e.g. <code>?first=firstname&last=lastname</code>.",
|
||||||
|
"TIP: In the following exercise we are going to receive data from a POST request, at the same <code>/name</code> route path. If you want you can use the method <code>app.route(path).get(handler).post(handler)</code>. This syntax allows you to chain different verb handlers on the same path route. You can save a bit of typing, and have cleaner code."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Test 1 : Your API endpoint should respond with the correct name",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/name?first=Mick&last=Jagger').then(data => { assert.equal(data.name, 'Mick Jagger', 'Test 1: \"GET /name\" route does not behave as expected') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Test 2 : Your APi endpoint should respond with the correct name",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/name?last=Richards&first=Keith').then(data => { assert.equal(data.name, 'Keith Richards', 'Test 2: \"GET /name\" route does not behave as expected') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb2367417b2b2512bf7",
|
||||||
|
"title": "Use body-parser to Parse POST Requests",
|
||||||
|
"description": [
|
||||||
|
"Besides GET there is another common http verb, it is POST. POST is the default method used to send client data with HTML forms. In the REST convention POST is used to send data to create new items in the database (a new user, or a new blog post). We don’t have a database in this project, but we are going to learn how to handle POST requests anyway.",
|
||||||
|
"In these kind of requests the data doesn’t appear in the URL, it is hidden in the request body. This is a part of the HTML request, also called payload. Since HTML is text based, even if you don’t see the data, it doesn’t mean that they are secret. The raw content of an HTTP POST request is shown below:",
|
||||||
|
"<blockquote>POST /path/subpath HTTP/1.0<br>From: john@example.com<br>User-Agent: someBrowser/1.0<br>Content-Type: application/x-www-form-urlencoded<br>Content-Length: 20<br>name=John+Doe&age=25</blockquote>",
|
||||||
|
"As you can see the body is encoded like the query string. This is the default format used by HTML forms. With Ajax we can also use JSON to be able to handle data having a more complex structure. There is also another type of encoding: multipart/form-data. This one is used to upload binary files.",
|
||||||
|
"In this exercise we will use an urlencoded body.",
|
||||||
|
"To parse the data coming from POST requests, you have to install a package: the body-parser. This package allows you to use a series of middleware, which can decode data in different formats. See the docs <a href=\"https://github.com/expressjs/body-parser\" target=\"_blank\" >here</a>.",
|
||||||
|
"Install the body-parser module in your package.json. Then require it at the top of the file. Store it in a variable named bodyParser.",
|
||||||
|
"The middleware to handle url encoded data is returned by <code>bodyParser.urlencoded({extended: false})</code>. <code>extended=false</code> is a configuration option that tells the parser to use the classic encoding. When using it, values can be only strings or arrays. The extended version allows more data flexibility, but it is outmatched by JSON. Pass to <code>app.use()</code> the function returned by the previous method call. As usual, the middleware must be mounted before all the routes which need it."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "The 'body-parser' middleware should be mounted",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/add-body-parser').then(data => { assert.isAbove(data.mountedAt, 0, '\"body-parser\" is not mounted correctly') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb2367417b2b2512bf8",
|
||||||
|
"title": "Get Data from POST Requests",
|
||||||
|
"description": [
|
||||||
|
"Mount a POST handler at the path <code>/name</code>. It’s the same path as before. We have prepared a form in the html frontpage. It will submit the same data of exercise 10 (Query string). If the body-parser is configured correctly, you should find the parameters in the object <code>req.body</code>. Have a look at the usual library example:",
|
||||||
|
"<blockquote>route: POST '/library'<br>urlencoded_body: userId=546&bookId=6754 <br>req.body: {userId: '546', bookId: '6754'}</blockquote>",
|
||||||
|
"Respond with the same JSON object as before: <code>{name: 'firstname lastname'}</code>. Test if your endpoint works using the html form we provided in the app frontpage.",
|
||||||
|
"Tip: There are several other http methods other than GET and POST. And by convention there is a corrispondence between the http verb, and the operation you are going to execute on the server. The conventional mapping is:",
|
||||||
|
"POST (sometimes PUT) - Create a new resource using the information sent with the request,",
|
||||||
|
"GET - Read an existing resource without modifying it,",
|
||||||
|
"PUT or PATCH (sometimes POST) - Update a resource using the data sent,",
|
||||||
|
"DELETE => Delete a resource.",
|
||||||
|
"There are also a couple of other methods which are used to negotiate a connection with the server. Except from GET, all the other methods listed above can have a payload (i.e. the data into the request body). The body-parser middleware works with these methods as well."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Test 1 : Your API endpoint should respond with the correct name",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/name', {first: 'Mick', last: 'Jagger'}).then(data => { assert.equal(data.name, 'Mick Jagger', 'Test 1: \"POST /name\" route does not behave as expected') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "Test 2 : Your API endpoint should respond with the correct name",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/name', {first: 'Keith', last: 'Richards'}).then(data => { assert.equal(data.name, 'Keith Richards', 'Test 2: \"POST /name\" route does not behave as expected') }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,282 @@
|
|||||||
|
{
|
||||||
|
"name": "Managing Packages with npm",
|
||||||
|
"order": 1,
|
||||||
|
"time": "5 hours",
|
||||||
|
"helpRoom": "Help",
|
||||||
|
"challenges": [
|
||||||
|
{
|
||||||
|
"id": "587d7fb3367417b2b2512bfb",
|
||||||
|
"title": "How to Use package.json, the Core of Any Node.js Project or npm Package",
|
||||||
|
"description": [
|
||||||
|
"The file package.json is the center of any Node.js project or npm package. It stores information about your project just like the <head>-section in a HTML document describes the content of a webpage. The package.json consists of a single JSON-object where information is stored in \"key\": value-pairs. There are only two required fields in a minimal package.json - name and version - but it’s a good practice to provide additional information about your project that could be useful to future users or maintainers.",
|
||||||
|
"The author-field",
|
||||||
|
"If you go to the Glitch project that you set up previously and look at on the left side of your screen, you’ll find the file tree where you can see an overview of the various files in your project. Under the file tree’s back-end section, you’ll find package.json - the file that we’ll be improving in the next couple of challenges.",
|
||||||
|
"One of the most common pieces of information in this file is the author-field that specifies who’s the creator of a project. It can either be a string or an object with contact details. The object is recommended for bigger projects but in our case, a simple string like the following example will do.",
|
||||||
|
"<code>\"author\": \"Jane Doe\",</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Add your name to the author-field in the package.json of your Glitch project.",
|
||||||
|
"Remember that you’re writing JSON.",
|
||||||
|
"All field-names must use double-quotes (\"), e.g. \"author\"",
|
||||||
|
"All fields must be separated with a comma (,)"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "package.json should have a valid \"author\" key",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert(packJson.author, '\"author\" is missing'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb3367417b2b2512bfc",
|
||||||
|
"title": "Add a Description to Your package.json",
|
||||||
|
"description": [
|
||||||
|
"The next part of a good package.json is the description-field, where a short but informative description about your project belongs.",
|
||||||
|
"If you some day plan to publishing a package to npm, remember that this is the string that should sell your idea to the user when they decide whether to install your package or not. However, that’s not the only use case for the description: Since it’s a great way to summarize what a project does, it’s just as important for your normal Node.js-projects to help other developers, future maintainers or even your future self understand the project quickly.",
|
||||||
|
"Regardless of what you plan for your project, a description is definitely recommended. Let’s add something similar to this:",
|
||||||
|
"<code>\"description\": \"A project that does something awesome\",</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Add a description to the package.json in your Glitch project.",
|
||||||
|
"Remember to use double-quotes for field-names (\") and commas (,) to separate fields."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "package.json should have a valid \"description\" key",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert(packJson.description, '\"description\" is missing'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb4367417b2b2512bfd",
|
||||||
|
"title": "Add Keywords to Your package.json",
|
||||||
|
"description": [
|
||||||
|
"The keywords-field is where you can describe your project using related keywords.",
|
||||||
|
"Example",
|
||||||
|
"<code>\"keywords\": [ \"descriptive\", \"related\", \"words\" ],</code>",
|
||||||
|
"As you can see, this field is structured as an array of double-quoted strings.",
|
||||||
|
"Instructions",
|
||||||
|
"Add an array of suitable strings to the keywords-field in the package.json of your Glitch project.",
|
||||||
|
"One of the keywords should be freecodecamp."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "package.json should have a valid \"keywords\" key",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert(packJson.keywords, '\"keywords\" is missing'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"keywords\" field should be an Array",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.isArray(packJson.keywords, '\"keywords\" is not an array'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"keywords\" should include \"freecodecamp\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.include(packJson.keywords, 'freecodecamp', '\"keywords\" does not include \"freecodecamp\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb4367417b2b2512bfe",
|
||||||
|
"title": "Add a License to Your package.json",
|
||||||
|
"description": [
|
||||||
|
"TODO: This challenge could be used to inspire more people to develop OSS - we should really improve this description.",
|
||||||
|
"The license-field is where you inform users of your project what they are allowed to do with it.",
|
||||||
|
"Some common licenses for open source projects include MIT and BSD. http://choosealicense.com is a great resource if you want to learn more about what license could fit your project.",
|
||||||
|
"License information is not required. Copyright laws in most countries will give you ownership of what you create by default. However, it’s always a good practice to explicitly state what users can and can’t do.",
|
||||||
|
"Example",
|
||||||
|
"<code>\"license\": \"MIT\",</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Fill the license-field in the package.json of your Glitch project as you find suitable."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "package.json should have a valid \"license\" key",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert(packJson.license, '\"license\" is missing'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb4367417b2b2512bff",
|
||||||
|
"title": "Add a Version to Your package.json",
|
||||||
|
"description": [
|
||||||
|
"The version is together with name one of the required fields in a package.json. This field describes the current version of your project.",
|
||||||
|
"Example",
|
||||||
|
"<code>\"version\": \"1.2\",</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Add a version to the package.json in your Glitch project."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "package.json should have a valid \"version\" key",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert(packJson.version, '\"version\" is missing'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb4367417b2b2512c00",
|
||||||
|
"title": "Expand Your Project with External Packages from npm",
|
||||||
|
"description": [
|
||||||
|
"One of the biggest reasons to use a package manager is their powerful dependency management. Instead of manually having to make sure that you get all dependencies whenever you set up a project on a new computer, npm automatically installs everything for you. But how can npm know exactly what your project needs? Meet the dependencies-section of your package.json.",
|
||||||
|
"In the dependencies-section, packages your project require are stored using the following format:",
|
||||||
|
"<code>\"dependencies\": {</code>",
|
||||||
|
"<code> \"package-name\": \"version\",</code>",
|
||||||
|
"<code> \"express\": \"4.14.0\"</code>",
|
||||||
|
"<code>}</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Add version 2.14.0 of the package moment to the dependencies-field of your package.json",
|
||||||
|
"Moment is a handy library for working with time and dates."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "\"dependencies\" should include \"moment\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'moment', '\"dependencies\" does not include \"moment\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"moment\" version should be \"2.14.0\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.match(packJson.dependencies.moment, /^[\\^\\~]?2\\.14\\.0/, 'Wrong version of \"moment\" installed. It should be 2.14.0'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb5367417b2b2512c01",
|
||||||
|
"title": "Manage npm Dependencies By Understanding Semantic Versioning",
|
||||||
|
"description": [
|
||||||
|
"Versions of the npm packages in the dependencies-section of your package.json follow what’s called Semantic Versioning (SemVer), an industry standard for software versioning aiming to make it easier to manage dependencies. Libraries, frameworks or other tools published on npm should use SemVer in order to clearly communicate what kind of changes that projects who depend on the package can expect if they update.",
|
||||||
|
"SemVer doesn’t make sense in projects without public APIs - so unless your project is similar to the examples above, use another versioning format.",
|
||||||
|
"So why do you need to understand SemVer?",
|
||||||
|
"Knowing SemVer can be useful when you develop software that use external dependencies (which you almost always do). One day, your understanding of these numbers will save you from accidentally introducing breaking changes to your project without understanding why things “that worked yesterday” suddenly doesn’t.",
|
||||||
|
"This is how Semantic Versioning works according to the official website:",
|
||||||
|
"Given a version number MAJOR.MINOR.PATCH, increment the:",
|
||||||
|
"MAJOR version when you make incompatible API changes,",
|
||||||
|
"MINOR version when you add functionality in a backwards-compatible manner, and",
|
||||||
|
"PATCH version when you make backwards-compatible bug fixes.",
|
||||||
|
"This means that PATCHes are bug fixes and MINORs add new features but neither of them break what worked before. Finally, MAJORs add changes that won’t work with earlier versions.",
|
||||||
|
"Example",
|
||||||
|
"A semantic version number: 1.3.8",
|
||||||
|
"Instructions",
|
||||||
|
"In the dependencies-section of your package.json, change the version of moment to match MAJOR version 2, MINOR version 10 and PATCH version 2"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "\"dependencies\" should include \"moment\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'moment', '\"dependencies\" does not include \"moment\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"moment\" version should be \"2.10.2\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.match(packJson.dependencies.moment, /^[\\^\\~]?2\\.10\\.2/, 'Wrong version of \"moment\". It should be 2.10.2'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb5367417b2b2512c02",
|
||||||
|
"title": "Use the Tilde-Character to Always Use the Latest Patch Version of a Dependency",
|
||||||
|
"description": [
|
||||||
|
"In the last challenge, we told npm to only include a specific version of a package. That’s a useful way to freeze your dependencies if you need to make sure that different parts of your project stay compatible with each other. But in most use cases you don’t want to miss bug fixes, since they often include important security patches and (hopefully) don’t break things in doing so.",
|
||||||
|
"To allow a npm dependency to get updated to the latest PATCH-version, you can prefix the dependency’s version with the tilde-character (~). In package.json, our current rule for how npm may upgrade moment is to use a specific version only (2.10.2), but we want to allow the latest 2.10.x-version.",
|
||||||
|
"Example",
|
||||||
|
"<code>\"some-package-name\": \"~1.3.8\" allows updates to any 1.3.x version.</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Use the tilde-character (~) to prefix the version of moment in your dependencies and allow npm to update it to any new PATCH release.",
|
||||||
|
"Note that the version numbers themselves not should be changed."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "\"dependencies\" should include \"moment\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'moment', '\"dependencies\" does not include \"moment\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"moment\" version should match \"~2.10.2\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.match(packJson.dependencies.moment, /^\\~2\\.10\\.2/, 'Wrong version of \"moment\". It should be ~2.10.2'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb5367417b2b2512c03",
|
||||||
|
"title": "Use the Caret-Character to Use the Latest Minor Version of a Dependency",
|
||||||
|
"description": [
|
||||||
|
"Similar to how the tilde (~) we learned about in the last challenge allow npm to install the latest PATCH for a dependency, the caret (^) allows npm to install future updates as well. The difference is that the caret will allow both MINOR updates and PATCHes.",
|
||||||
|
"At the moment, your current version of moment should be ~2.10.2 which allows npm to install to the latest 2.10.x-version. If we instead were to use the caret (^) as our version prefix, npm would instead be allowed to update to any 2.x.x-version.",
|
||||||
|
"Example",
|
||||||
|
"<code>\"some-package-name\": \"^1.3.8\" allows updates to any 1.x.x version.</code>",
|
||||||
|
"Instructions",
|
||||||
|
"Use the caret-character (^) to prefix the version of moment in your dependencies and allow npm to update it to any new MINOR release.",
|
||||||
|
"Note that the version numbers themselves not should be changed."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "\"dependencies\" should include \"moment\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'moment', '\"dependencies\" does not include \"moment\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"moment\" version should match \"^2.x.x\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.match(packJson.dependencies.moment, /^\\^2\\./, 'Wrong version of \"moment\". It should be ^2.10.2'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb5367417b2b2512c04",
|
||||||
|
"title": "Remove a Package from Your Dependencies",
|
||||||
|
"description": [
|
||||||
|
"Now you’ve tested a few ways you can manage dependencies of your project by using the package.json's dependencies-section. You’ve included external packages by adding them to the file and even told npm what types of versions you want by using special characters as the tilde (~) or the caret (^).",
|
||||||
|
"But what if you want to remove an external package that you no longer need? You might already have guessed it - Just remove the corresponding \"key\": value-pair for that from your dependencies.",
|
||||||
|
"This same method applies to removing other fields in your package.json as well",
|
||||||
|
"Instructions",
|
||||||
|
"Remove the package moment from your dependencies.",
|
||||||
|
"Make sure you have the right amount of commas after removing it."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "\"dependencies\" should not include \"moment\"",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/package.json').then(data => { var packJson = JSON.parse(data); assert.notProperty(packJson.dependencies, 'moment', '\"dependencies\" still includes \"moment\"'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -0,0 +1,260 @@
|
|||||||
|
{
|
||||||
|
"name": "MongoDB and Mongoose",
|
||||||
|
"order": 3,
|
||||||
|
"time": "5 hours",
|
||||||
|
"helpRoom": "Help",
|
||||||
|
"challenges": [
|
||||||
|
{
|
||||||
|
"id": "587d7fb6367417b2b2512c06",
|
||||||
|
"title": "Install and Set Up Mongoose",
|
||||||
|
"description": [
|
||||||
|
"Add mongodb and mongoose to the project’s package.json. Then require mongoose. Store your mLab database URI in the private .env file as MONGO_URI. Connect to the database using mongoose.connect(<Your URI>)"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "\"mongodb\" dependency should be in package.json",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/file/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'mongodb'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"text": "\"mongoose\" dependency should be in package.json",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/file/package.json').then(data => { var packJson = JSON.parse(data); assert.property(packJson.dependencies, 'mongoose'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb6367417b2b2512c07",
|
||||||
|
"title": "Create a Model",
|
||||||
|
"description": [
|
||||||
|
"First of all we need a Schema. Each schema maps to a MongoDB collection. It defines the shape of the documents within that collection.",
|
||||||
|
"Schemas are building block for Models. They can be nested to create complex models, but in this case we’ll keep things simple.",
|
||||||
|
"A model allows you to create instances of your objects, called documents.",
|
||||||
|
"Create a person having this prototype :",
|
||||||
|
"<code>- Person Prototype -</code>",
|
||||||
|
"<code>--------------------</code>",
|
||||||
|
"<code>name : string [required]</code>",
|
||||||
|
"<code>age : number</code>",
|
||||||
|
"<code>favoriteFoods : array of strings (*) </code>",
|
||||||
|
"Use the mongoose basic schema types. If you want you can also add",
|
||||||
|
"more fields, use simple validators like required or unique,",
|
||||||
|
"and set default values. See the <a href='http://mongoosejs.com/docs/guide.html'>mongoose docs</a>.",
|
||||||
|
"[C]RUD Part I - CREATE",
|
||||||
|
"Note: Glitch is a real server, and in real servers the interactions with the db happen in handler functions. These function are executed when some event happens (e.g. someone hits an endpoint on your API). We’ll follow the same approach in these exercises. The done() function is a callback that tells us that we can proceed after completing an asynchronous operation such as inserting, searching, updating or deleting. It’s following the Node convention and should be called as done(null, data) on success, or done(err) on error.",
|
||||||
|
"Warning - When interacting with remote services, errors may occur !",
|
||||||
|
"<code>/* Example */</code>",
|
||||||
|
"<code>var someFunc = function(done) {</code>",
|
||||||
|
"<code> //... do something (risky) ...</code>",
|
||||||
|
"<code> if(error) return done(error);</code>",
|
||||||
|
"<code> done(null, result);</code>",
|
||||||
|
"<code>};</code>"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Creating an instance from a mongoose schema should succeed",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/mongoose-model', {name: 'Mike', age: 28, favoriteFoods: ['pizza', 'cheese']}).then(data => { assert.equal(data.name, 'Mike', '\"model.name\" is not what expected'); assert.equal(data.age, '28', '\"model.age\" is not what expected'); assert.isArray(data.favoriteFoods, '\"model.favoriteFoods\" is not an Array'); assert.include(data.favoriteFoods, 'pizza', '\"model.favoriteFoods\" does not include the expected items'); assert.include(data.favoriteFoods, 'cheese', '\"model.favoriteFoods\" does not include the expected items'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb6367417b2b2512c09",
|
||||||
|
"title": "Create and Save a Record of a Model",
|
||||||
|
"description": [
|
||||||
|
"Create a document instance using the Person constructor you build before. Pass to the constructor an object having the fields name, age, and favoriteFoods. Their types must be conformant to the ones in the Person Schema. Then call the method document.save() on the returned document instance. Pass to it a callback using the Node convention. This is a common pattern, all the following CRUD methods take a callback function like this as the last argument.",
|
||||||
|
"<code>/* Example */</code>",
|
||||||
|
"<code>// ...</code>",
|
||||||
|
"<code>person.save(function(err, data) {</code>",
|
||||||
|
"<code>// ...do your stuff here...</code>",
|
||||||
|
"<code>});</code>"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Creating and saving a db item should succeed",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/create-and-save-person').then(data => { assert.isString(data.name, '\"item.name\" should be a String'); assert.isNumber(data.age, '28', '\"item.age\" should be a Number'); assert.isArray(data.favoriteFoods, '\"item.favoriteFoods\" should be an Array'); assert.equal(data.__v, 0, 'The db item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb7367417b2b2512c0a",
|
||||||
|
"title": "Create Many Records with model.create()",
|
||||||
|
"description": [
|
||||||
|
"Sometimes you need to create many instances of your models, e.g. when seeding a database with initial data. Model.create() takes an array of objects like [{name: 'John', ...}, {...}, ...] as the first argument, and saves them all in the db. Create many people with Model.create(), using the function argument arrayOfPeople."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Creating many db items at once should succeed",
|
||||||
|
"testString": "getUserInput => $.ajax({url: getUserInput('url') + '/_api/create-many-people', type: 'POST', contentType:'application/json', data: JSON.stringify([{name: 'John', age: 24, favoriteFoods: ['pizza', 'salad']}, {name: 'Mary', age: 21, favoriteFoods: ['onions', 'chicken']}])}).then(data => { assert.isArray(data, 'the response should be an array'); assert.equal(data.length, 2, 'the response does not contain the expected number of items'); assert.equal(data[0].name, 'John', 'The first item is not correct'); assert.equal(data[0].__v, 0, 'The first item should be not previously edited'); assert.equal(data[1].name, 'Mary', 'The second item is not correct'); assert.equal(data[1].__v, 0, 'The second item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb7367417b2b2512c0b",
|
||||||
|
"title": "Use model.find() to Search Your Database",
|
||||||
|
"description": [
|
||||||
|
"Find all the people having a given name, using Model.find() -> [Person]",
|
||||||
|
"In its simplest usage, Model.find() accepts a query document (a JSON object ) as the first argument, then a callback. It returns an array of matches. It supports an extremely wide range of search options. Check it in the docs. Use the function argument personName as search key."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Find all items corresponding to a criteria should succeed",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-all-by-name', {name: 'r@nd0mN4m3', age: 24, favoriteFoods: ['pizza']}).then(data => { assert.isArray(data, 'the response should be an Array'); assert.equal(data[0].name, 'r@nd0mN4m3', 'item.name is not what expected'); assert.equal(data[0].__v, 0, 'The item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb7367417b2b2512c0c",
|
||||||
|
"title": "Use model.findOne() to Return a Single Matching Document from Your Database",
|
||||||
|
"description": [
|
||||||
|
"Model.findOne() behaves like .find(), but it returns only one document (not an array), even if there are items. It is especially useful when searching by properties that you have declared as unique. Find just one person which has a certain food in her favorites, using Model.findOne() -> Person. Use the function argument food as search key."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Find one item should succeed",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-one-by-food', {name: 'Gary', age: 46, favoriteFoods: ['chicken salad']}).then(data => { assert.equal(data.name, 'Gary', 'item.name is not what expected'); assert.deepEqual(data.favoriteFoods, ['chicken salad'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0, 'The item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb7367417b2b2512c0d",
|
||||||
|
"title": "Use model.findById() to Search Your Database By _id",
|
||||||
|
"description": [
|
||||||
|
"When saving a document, mongodb automatically adds the field _id, and set it to a unique alphanumeric key. Searching by _id is an extremely frequent operation, so moongose provides a dedicated method for it. Find the (only!!) person having a given _id, using Model.findById() -> Person. Use the function argument personId as search key."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Find an item by Id should succeed",
|
||||||
|
"testString": "getUserInput => $.get(getUserInput('url') + '/_api/find-by-id').then(data => { assert.equal(data.name, 'test', 'item.name is not what expected'); assert.equal(data.age, 0, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['none'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0, 'The item should be not previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb8367417b2b2512c0e",
|
||||||
|
"title": "Perform Classic Updates by Running Find, Edit, then Save",
|
||||||
|
"description": [
|
||||||
|
"In the good old days this was what you needed to do if you wanted to edit a document and be able to use it somehow e.g. sending it back in a server response. Mongoose has a dedicated updating method : Model.update(). It is binded to the low-level mongo driver. It can bulk edit many documents matching certain criteria, but it doesn’t send back the updated document, only a ‘status’ message. Furthermore it makes model validations difficult, because it just directly calls the mongo driver.",
|
||||||
|
"Find a person by _id ( use any of the above methods ) with the parameter personId as search key. Add “hamburger” to the list of her favoriteFoods (you can use Array.push()). Then - inside the find callback - save() the updated Person.",
|
||||||
|
"[*] Hint: This may be tricky if in your Schema you declared favoriteFoods as an Array, without specifying the type (i.e. [String]). In that casefavoriteFoods defaults to Mixed type, and you have to manually mark it as edited using document.markModified('edited-field'). (http://mongoosejs.com/docs/schematypes.html - #Mixed )"
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Find-edit-update an item should succeed",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-edit-save', {name:'Poldo', age: 40, favoriteFoods:['spaghetti']}).then(data => { assert.equal(data.name, 'Poldo', 'item.name is not what expected'); assert.equal(data.age, 40, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['spaghetti', 'hamburger'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 1, 'The item should be previously edited'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb8367417b2b2512c0f",
|
||||||
|
"title": "Perform New Updates on a Document Using model.findOneAndUpdate()",
|
||||||
|
"description": [
|
||||||
|
"Recent versions of mongoose have methods to simplify documents updating. Some more advanced features (i.e. pre/post hooks, validation) behave differently with this approach, so the Classic method is still useful in many situations. findByIdAndUpdate() can be used when searching by Id.",
|
||||||
|
"Find a person by Name and set her age to 20. Use the function parameter personName as search key.",
|
||||||
|
"Hint: We want you to return the updated document. o do that you need to pass the options document { new: true } as the 3rd argument to findOneAndUpdate(). By default these methods return the unmodified object."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "findOneAndUpdate an item should succeed",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/find-one-update', {name:'Dorian Gray', age: 35, favoriteFoods:['unknown']}).then(data => { assert.equal(data.name, 'Dorian Gray', 'item.name is not what expected'); assert.equal(data.age, 20, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['unknown'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0, 'findOneAndUpdate does not increment version by design !!!'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb8367417b2b2512c10",
|
||||||
|
"title": "Delete One Document Using model.findByIdAndRemove",
|
||||||
|
"description": [
|
||||||
|
"Delete one person by her _id. You should use one of the methods findByIdAndRemove() or findOneAndRemove(). They are like the previous update methods. They pass the removed document to the cb. As usual, use the function argument personId as search key."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Deleting an item should succeed",
|
||||||
|
"testString": "getUserInput => $.post(getUserInput('url') + '/_api/remove-one-person', {name:'Jason Bourne', age: 36, favoriteFoods:['apples']}).then(data => { assert.equal(data.name, 'Jason Bourne', 'item.name is not what expected'); assert.equal(data.age, 36, 'item.age is not what expected'); assert.deepEqual(data.favoriteFoods, ['apples'], 'item.favoriteFoods is not what expected'); assert.equal(data.__v, 0); assert.equal(data.count, 0, 'the db items count is not what expected'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb8367417b2b2512c11",
|
||||||
|
"title": "Delete Many Documents with model.remove()",
|
||||||
|
"description": [
|
||||||
|
"Model.remove() is useful to delete all the documents matching given criteria. Delete all the people whose name is “Mary”, using Model.remove(). Pass to it a query ducument with the “name” field set, and of course a callback.",
|
||||||
|
"Note: Model.remove() doesn’t return the deleted document, but a JSON object containing the outcome of the operation, and the number of items affected. Don’t forget to pass it to the done() callback, since we use it in tests."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Deleting many items at once should succeed",
|
||||||
|
"testString": "getUserInput => $.ajax({url: getUserInput('url') + '/_api/remove-many-people', type: 'POST', contentType:'application/json', data: JSON.stringify([{name: 'Mary', age: 16, favoriteFoods: ['lollipop']}, {name: 'Mary', age: 21, favoriteFoods: ['steak']}])}).then(data => { assert.isTrue(!!data.ok, 'The mongo stats are not what expected'); assert.equal(data.n, 2, 'The number of items affected is not what expected'); assert.equal(data.count, 0, 'the db items count is not what expected'); }, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "587d7fb9367417b2b2512c12",
|
||||||
|
"title": "Chain Search Query Helpers to Narrow Search Results",
|
||||||
|
"description": [
|
||||||
|
"If you don’t pass the callback as the last argument to Model.find() (or to the other search methods), the query is not executed. You can store the query in a variable for later use. This kind of object enables you to build up a query using chaining syntax. The actual db search is executed when you finally chain the method .exec(). Pass your callback to this last method. There are many query helpers, here we’ll use the most ‘famous’ ones.",
|
||||||
|
"Find people who like \"burrito\". Sort them by name, limit the results to two documents, and hide their age. Chain .find(), .sort(), .limit(), .select(), and then .exec(). Pass the done(err, data) callback to exec()."
|
||||||
|
],
|
||||||
|
"challengeSeed": [],
|
||||||
|
"tests": [
|
||||||
|
{
|
||||||
|
"text": "Chaining query helpers should succeed",
|
||||||
|
"testString": "getUserInput => $.ajax({url: getUserInput('url') + '/_api/query-tools', type: 'POST', contentType:'application/json', data: JSON.stringify([{name: 'Pablo', age: 26, favoriteFoods: ['burrito', 'hot-dog']}, {name: 'Ashley', age: 32, favoriteFoods: ['steak', 'burrito']}, {name: 'Mario', age: 51, favoriteFoods: ['burrito', 'prosciutto']} ]) }).then(data => { assert.isArray(data, 'the response should be an Array'); assert.equal(data.length, 2, 'the data array length is not what expected'); assert.notProperty(data[0], 'age', 'The returned first item has too many properties'); assert.equal(data[0].name, 'Ashley', 'The returned first item name is not what expected'); assert.notProperty(data[1], 'age', 'The returned second item has too many properties'); assert.equal(data[1].name, 'Mario', 'The returned second item name is not what expected');}, xhr => { throw new Error(xhr.responseText); })"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"solutions": [],
|
||||||
|
"hints": [],
|
||||||
|
"challengeType": 2,
|
||||||
|
"translations": {}
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
@ -5,6 +5,71 @@ superBlock: APIs and Microservices
|
|||||||
---
|
---
|
||||||
## Introduction to the MongoDB and Mongoose Challenges
|
## Introduction to the MongoDB and Mongoose Challenges
|
||||||
|
|
||||||
MongoDB is a database that stores data records (documents) for use by an application. Mongo is a non-relational, "NoSQL" database. This means Mongo stores all data associated within one record, instead of storing it across many preset tables as in a SQL database. Some benefits of this storage model are:<br><br><ul><li>Scalability: by default, non-relational databases are split (or "sharded") across many systems instead of only one. This makes it easier to improve performance at a lower cost.</li><li>Flexibility: new datasets and properties can be added to a document without the need to make a new table for that data.</li><li>Replication: copies of the database run in parallel so if one goes down, one of the copies becomes the new primary data source.</li></ul><br>While there are many non-relational databases, Mongo's use of JSON as its document storage structure makes it a logical choice when learning backend JavaScript. Accessing documents and their properties is like accessing objects in JavaScript.<br><br>Mongoose.js is an npm module for Node.js that allows you to write objects for Mongo as you would in JavaScript. This can make is easier to construct documents for storage in Mongo.
|
MongoDB is a database that stores data records (documents) for use by an application. Mongo is a non-relational, "NoSQL" database. This means Mongo stores all data associated within one record, instead of storing it across many preset tables as in a SQL database. Some benefits of this storage model are:
|
||||||
Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.<br>Start this project on Glitch using <a href='https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-mongomongoose/'>this link</a> or clone <a href='https://github.com/freeCodeCamp/boilerplate-mongomongoose/'>this repository</a> on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
|
||||||
|
|
||||||
|
- Scalability: by default, non-relational databases are split (or "sharded") across many systems instead of only one. This makes it easier to improve performance at a lower cost.
|
||||||
|
- Flexibility: new datasets and properties can be added to a document without the need to make a new table for that data.
|
||||||
|
- Replication: copies of the database run in parallel so if one goes down, one of the copies becomes the new primary data source.
|
||||||
|
|
||||||
|
While there are many non-relational databases, Mongo's use of JSON as its document storage structure makes it a logical choice when learning backend JavaScript. Accessing documents and their properties is like accessing objects in JavaScript.
|
||||||
|
|
||||||
|
Mongoose.js is an npm module for Node.js that allows you to write objects for Mongo as you would in JavaScript. This can make is easier to construct documents for storage in Mongo.
|
||||||
|
|
||||||
|
Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.
|
||||||
|
|
||||||
|
Start this project on Glitch using [this link](https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-mongomongoose/) or clone [this repository](https://github.com/freeCodeCamp/boilerplate-mongomongoose/) on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
||||||
|
|
||||||
|
## Use mLab to host a free mongodb instance for your projects
|
||||||
|
|
||||||
|
For the following challenges, we are going to start using MongoDB to store our data. To simplify the configuration, we are going to use mLab.
|
||||||
|
|
||||||
|
mLab is a MongoDB Database-as-a-Service platform, which basically means that they configure and host the database for you, making it so the only responsibility you have is to populate your database with what matters: data!
|
||||||
|
We are going to show you how to:
|
||||||
|
|
||||||
|
- Create an mLab account.
|
||||||
|
- Create a free online database.
|
||||||
|
- Create a new admin user on the database, so you can access it.
|
||||||
|
- Get the mLab URI, which you will use in your application to connect to your database.
|
||||||
|
|
||||||
|
### Create an mLab account
|
||||||
|
|
||||||
|
Let's start by <a href='https://mlab.com/' target='_blank' rel='no-follow'>going to mLab</a>.
|
||||||
|
|
||||||
|
Once you open the mLab page, you should sign up for a new account.
|
||||||
|
|
||||||
|
- Click the <a href='https://mlab.com/signup/' target='_blank' rel='no-follow'>Sign Up</a> button in the top right corner to open the registration page.
|
||||||
|
- Fill the registration form with your information and send it.
|
||||||
|
- You should be logged into your new, unverified account.
|
||||||
|
- In the top of the screen, a message should appear asking to send you an e-mail for account verification. Send and confirm it.
|
||||||
|
- After you confirm your account, click **Create new** in the *MongoDB Deployments* section.
|
||||||
|
|
||||||
|
### Create a free online database.
|
||||||
|
|
||||||
|
Now we are going to create the actual database you are going to be using.
|
||||||
|
|
||||||
|
- Choose a *Cloud Provider* from the available list.
|
||||||
|
- Select the *Sandbox* plan type, which is the only one with no cost, and press **Continue**.
|
||||||
|
- Select a region for your Sandbox, from the available list, and press **Continue**.
|
||||||
|
- Input a name for your database. This name will be used in the URI for your database. After that, press **Continue**.
|
||||||
|
|
||||||
|
A summary of all your choices should appear, allowing you to change any information provided in the previous steps. Press **Submit Order** to confirm the information.
|
||||||
|
|
||||||
|
|
||||||
|
### Create a new admin user on the database
|
||||||
|
|
||||||
|
After you confirmed your configuration, a new sandbox should have been created in the *MongoDB Deployments* section. We are now going to create an administrator, so you can use the database in your application.
|
||||||
|
|
||||||
|
- Click the newly-created database.
|
||||||
|
- Click the *Users* section.
|
||||||
|
- Click the **Add database user** button.
|
||||||
|
- Input an username and password for your administrator. Do not mark it as read-only or you will not be able to add any information to the database with this user.
|
||||||
|
|
||||||
|
### Get the mLab URI
|
||||||
|
|
||||||
|
Almost done! We have created our new database and created an user to access it, so we just need to find a way to use it in our applications.
|
||||||
|
|
||||||
|
- In your database page, you should see some instructions about connecting using the standard MongoDB URI.
|
||||||
|
- The line should look like this `mongodb://dbuser:dbpassword@ds0<PORT>.mlab.com:<PORT>/<DATABASE-NAME>`.
|
||||||
|
- Copy this URI and substitute dbuser and dbpassword with the information for the user you previously created in the database.
|
||||||
|
- That's it! This is the URI you will add to your application to connect to your database. Keep this URI safe somewhere, so you can use it later!
|
||||||
|
- Feel free to create separate databases for different applications, if they don't have an use for the same data. You just need to create the sandbox, user and obtain the new URI.
|
||||||
|
@ -5,4 +5,7 @@ superBlock: Information Security and Quality Assurance
|
|||||||
---
|
---
|
||||||
## Introduction to Advanced Node and Express Challenges
|
## Introduction to Advanced Node and Express Challenges
|
||||||
|
|
||||||
This is a stub introduction
|
*Authentication* is the process or action of verifying the identity of a user or process. Up to this point you have not been able to create an app utilizing this key concept.
|
||||||
|
|
||||||
|
The most common and easiest to use authentication middleware for Node.js is [Passport](http://passportjs.org/). It is easy to learn, light-weight, and extremely flexible allowing for many *strategies*, which we will talk about in later challenges. In addition to authentication we will also look at template engines which allow for use of *Pug* and web sockets which allow for real time communication between all your clients and your server. Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.
|
||||||
|
Start this project on Glitch using [this link](https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-advancednode/) or clone [this repository](https://github.com/freeCodeCamp/boilerplate-advancednode/) on GitHub! If you use Glitch, remember to save the link to your project somewhere safe
|
@ -5,4 +5,8 @@ superBlock: Information Security and Quality Assurance
|
|||||||
---
|
---
|
||||||
## Introduction to Quality Assurance with Chai Challenges
|
## Introduction to Quality Assurance with Chai Challenges
|
||||||
|
|
||||||
This is a stub introduction
|
As your programs become more complex, you need to test them often to make sure any new code you add doesn't break the program's original functionality. Chai is a JavaScript testing library that helps you check that your program still behaves the way you expect it to after you make changes. Using Chai, you can write tests that describe your program's requirements and see if your program meets them.
|
||||||
|
|
||||||
|
Working on these challenges will involve you writing your code on Glitch on our starter project. After completing each challenge you can copy your public glitch url (to the homepage of your app) into the challenge screen to test it! Optionally you may choose to write your project on another platform but it must be publicaly visible for our testing.
|
||||||
|
|
||||||
|
Start this project on Glitch using [this link](https://glitch.com/#!/import/github/freeCodeCamp/boilerplate-mochachai/) or clone [this repository](https://github.com/freeCodeCamp/boilerplate-mochachai/) on GitHub! If you use Glitch, remember to save the link to your project somewhere safe!
|
@ -1,155 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
|
|
||||||
import Codemirror from 'react-codemirror';
|
|
||||||
import NoSSR from 'react-no-ssr';
|
|
||||||
import MouseTrap from 'mousetrap';
|
|
||||||
|
|
||||||
import ns from './ns.json';
|
|
||||||
import CodeMirrorSkeleton from '../../Code-Mirror-Skeleton.jsx';
|
|
||||||
import {
|
|
||||||
executeChallenge,
|
|
||||||
modernEditorUpdated,
|
|
||||||
challengeMetaSelector
|
|
||||||
} from '../../redux';
|
|
||||||
|
|
||||||
import { themeSelector } from '../../../../redux';
|
|
||||||
|
|
||||||
import { createFileSelector } from '../../../../files';
|
|
||||||
|
|
||||||
const envProps = typeof window !== 'undefined' ? Object.keys(window) : [];
|
|
||||||
const options = {
|
|
||||||
lint: {
|
|
||||||
esversion: 6,
|
|
||||||
predef: envProps
|
|
||||||
},
|
|
||||||
lineNumbers: true,
|
|
||||||
mode: 'javascript',
|
|
||||||
runnable: true,
|
|
||||||
matchBrackets: true,
|
|
||||||
autoCloseBrackets: true,
|
|
||||||
scrollbarStyle: 'null',
|
|
||||||
lineWrapping: true,
|
|
||||||
gutters: [ 'CodeMirror-lint-markers' ]
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
createFileSelector((_, { fileKey }) => fileKey || ''),
|
|
||||||
challengeMetaSelector,
|
|
||||||
themeSelector,
|
|
||||||
(
|
|
||||||
file,
|
|
||||||
{ mode },
|
|
||||||
theme
|
|
||||||
) => ({
|
|
||||||
content: file.contents || '// Happy Coding!',
|
|
||||||
file: file,
|
|
||||||
mode: file.ext || mode || 'javascript',
|
|
||||||
theme
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapDispatchToProps = {
|
|
||||||
executeChallenge,
|
|
||||||
modernEditorUpdated
|
|
||||||
};
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
content: PropTypes.string,
|
|
||||||
executeChallenge: PropTypes.func.isRequired,
|
|
||||||
fileKey: PropTypes.string,
|
|
||||||
mode: PropTypes.string,
|
|
||||||
modernEditorUpdated: PropTypes.func.isRequired,
|
|
||||||
theme: PropTypes.string
|
|
||||||
};
|
|
||||||
|
|
||||||
export class Editor extends PureComponent {
|
|
||||||
createOptions = createSelector(
|
|
||||||
state => state.executeChallenge,
|
|
||||||
state => state.mode,
|
|
||||||
state => state.cmTheme,
|
|
||||||
(executeChallenge, mode, cmTheme) => ({
|
|
||||||
...options,
|
|
||||||
theme: cmTheme,
|
|
||||||
mode,
|
|
||||||
// JSHint only works with javascript
|
|
||||||
// we will need to switch to eslint to make this work with jsx
|
|
||||||
lint: mode === 'javascript' ? options.lint : false,
|
|
||||||
extraKeys: {
|
|
||||||
Esc() {
|
|
||||||
document.activeElement.blur();
|
|
||||||
},
|
|
||||||
Tab(cm) {
|
|
||||||
if (cm.somethingSelected()) {
|
|
||||||
return cm.indentSelection('add');
|
|
||||||
}
|
|
||||||
const spaces = Array(cm.getOption('indentUnit') + 1).join(' ');
|
|
||||||
return cm.replaceSelection(spaces);
|
|
||||||
},
|
|
||||||
'Shift-Tab': function(cm) {
|
|
||||||
return cm.indentSelection('subtract');
|
|
||||||
},
|
|
||||||
'Ctrl-Enter': function() {
|
|
||||||
executeChallenge();
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
'Cmd-Enter': function() {
|
|
||||||
executeChallenge();
|
|
||||||
return false;
|
|
||||||
},
|
|
||||||
'Ctrl-/': function(cm) {
|
|
||||||
cm.toggleComment();
|
|
||||||
},
|
|
||||||
'Cmd-/': function(cm) {
|
|
||||||
cm.toggleComment();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
componentDidMount() {
|
|
||||||
MouseTrap.bind('e', () => {
|
|
||||||
this.refs.editor.focus();
|
|
||||||
}, 'keyup');
|
|
||||||
}
|
|
||||||
|
|
||||||
componentWillUnmount() {
|
|
||||||
MouseTrap.unbind('e', 'keyup');
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
content,
|
|
||||||
modernEditorUpdated,
|
|
||||||
executeChallenge,
|
|
||||||
fileKey,
|
|
||||||
mode
|
|
||||||
} = this.props;
|
|
||||||
const cmTheme = this.props.theme === 'default' ? 'default' : 'dracula';
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
className={ `${ns}-editor` }
|
|
||||||
role='main'
|
|
||||||
>
|
|
||||||
<NoSSR onSSR={ <CodeMirrorSkeleton content={ content } /> }>
|
|
||||||
<Codemirror
|
|
||||||
onChange={ content => modernEditorUpdated(fileKey, content) }
|
|
||||||
options={ this.createOptions({ executeChallenge, mode, cmTheme }) }
|
|
||||||
ref='editor'
|
|
||||||
value={ content }
|
|
||||||
/>
|
|
||||||
</NoSSR>
|
|
||||||
</div>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Editor.displayName = 'Editor';
|
|
||||||
Editor.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(Editor);
|
|
@ -1,111 +0,0 @@
|
|||||||
import _ from 'lodash';
|
|
||||||
import React from 'react';
|
|
||||||
import { connect } from 'react-redux';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { addNS } from 'berkeleys-redux-utils';
|
|
||||||
|
|
||||||
import ns from './ns.json';
|
|
||||||
import Editor from './Editor.jsx';
|
|
||||||
import ChildContainer from '../../Child-Container.jsx';
|
|
||||||
import { showPreviewSelector, types } from '../../redux';
|
|
||||||
import SidePanel from '../../Side-Panel.jsx';
|
|
||||||
import Preview from '../../Preview.jsx';
|
|
||||||
import _Map from '../../../../Map';
|
|
||||||
import Panes from '../../../../Panes';
|
|
||||||
import { filesSelector } from '../../../../files';
|
|
||||||
|
|
||||||
const createModernEditorToggleType = fileKey =>
|
|
||||||
types.toggleModernEditor + `(${fileKey})`;
|
|
||||||
|
|
||||||
const getFirstFileKey = _.flow(_.values, _.first, _.property('key'));
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
nameToFileKey: PropTypes.object
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
filesSelector,
|
|
||||||
files => {
|
|
||||||
if (Object.keys(files).length === 1) {
|
|
||||||
return { nameToFileKey: { Editor: getFirstFileKey(files) }};
|
|
||||||
}
|
|
||||||
return {
|
|
||||||
nameToFileKey: _.reduce(files, (map, file) => {
|
|
||||||
map[file.name] = file.key;
|
|
||||||
return map;
|
|
||||||
}, {})
|
|
||||||
};
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapDispatchToProps = null;
|
|
||||||
|
|
||||||
export const mapStateToPanes = addNS(
|
|
||||||
ns,
|
|
||||||
createSelector(
|
|
||||||
filesSelector,
|
|
||||||
showPreviewSelector,
|
|
||||||
(files, showPreview) => {
|
|
||||||
// create panes map here
|
|
||||||
// must include map
|
|
||||||
// side panel
|
|
||||||
// editors are created based on state
|
|
||||||
// so pane component can have multiple panes based on state
|
|
||||||
|
|
||||||
const panesMap = {
|
|
||||||
[types.toggleMap]: 'Map',
|
|
||||||
[types.toggleSidePanel]: 'Lesson'
|
|
||||||
};
|
|
||||||
|
|
||||||
// If there is more than one file show file name
|
|
||||||
if (Object.keys(files).length > 1) {
|
|
||||||
_.forEach(files, (file) => {
|
|
||||||
panesMap[createModernEditorToggleType(file.fileKey)] = file.name;
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
const key = getFirstFileKey(files);
|
|
||||||
panesMap[createModernEditorToggleType(key)] = 'Editor';
|
|
||||||
}
|
|
||||||
|
|
||||||
if (showPreview) {
|
|
||||||
panesMap[types.togglePreview] = 'Preview';
|
|
||||||
}
|
|
||||||
|
|
||||||
return panesMap;
|
|
||||||
}
|
|
||||||
)
|
|
||||||
);
|
|
||||||
|
|
||||||
const nameToComponent = {
|
|
||||||
Map: _Map,
|
|
||||||
Lesson: SidePanel,
|
|
||||||
Preview: Preview
|
|
||||||
};
|
|
||||||
|
|
||||||
export function ShowModern({ nameToFileKey }) {
|
|
||||||
return (
|
|
||||||
<ChildContainer isFullWidth={ true }>
|
|
||||||
<Panes
|
|
||||||
render={ name => {
|
|
||||||
const Comp = nameToComponent[name];
|
|
||||||
if (Comp) {
|
|
||||||
return <Comp />;
|
|
||||||
}
|
|
||||||
if (nameToFileKey[name]) {
|
|
||||||
return <Editor fileKey={ nameToFileKey[name] } />;
|
|
||||||
}
|
|
||||||
return <span>Could not find Component for { name }</span>;
|
|
||||||
}}
|
|
||||||
/>
|
|
||||||
</ChildContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowModern.displayName = 'ShowModern';
|
|
||||||
ShowModern.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default connect(
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToProps
|
|
||||||
)(ShowModern);
|
|
@ -1 +0,0 @@
|
|||||||
export { default, mapStateToPanes } from './Show.jsx';
|
|
@ -1 +0,0 @@
|
|||||||
"modern"
|
|
@ -1,184 +0,0 @@
|
|||||||
import React, { PureComponent } from 'react';
|
|
||||||
import PropTypes from 'prop-types';
|
|
||||||
import { createSelector } from 'reselect';
|
|
||||||
import { reduxForm } from 'redux-form';
|
|
||||||
import {
|
|
||||||
Button,
|
|
||||||
Col,
|
|
||||||
Row
|
|
||||||
} from 'react-bootstrap';
|
|
||||||
|
|
||||||
import ChallengeTitle from '../../Challenge-Title.jsx';
|
|
||||||
import ChallengeDescription from '../../Challenge-Description.jsx';
|
|
||||||
import SolutionInput from '../../Solution-Input.jsx';
|
|
||||||
import TestSuite from '../../Test-Suite.jsx';
|
|
||||||
import Output from '../../Output.jsx';
|
|
||||||
import {
|
|
||||||
executeChallenge,
|
|
||||||
testsSelector,
|
|
||||||
outputSelector
|
|
||||||
} from '../../redux';
|
|
||||||
import { descriptionRegex } from '../../utils';
|
|
||||||
|
|
||||||
import {
|
|
||||||
createFormValidator,
|
|
||||||
isValidURL,
|
|
||||||
makeRequired
|
|
||||||
} from '../../../../utils/form.js';
|
|
||||||
import { challengeSelector } from '../../../../redux';
|
|
||||||
|
|
||||||
// provided by redux form
|
|
||||||
const reduxFormPropTypes = {
|
|
||||||
fields: PropTypes.object,
|
|
||||||
handleSubmit: PropTypes.func.isRequired,
|
|
||||||
resetForm: PropTypes.func.isRequired,
|
|
||||||
submitting: PropTypes.bool
|
|
||||||
};
|
|
||||||
|
|
||||||
const propTypes = {
|
|
||||||
description: PropTypes.arrayOf(PropTypes.string),
|
|
||||||
executeChallenge: PropTypes.func.isRequired,
|
|
||||||
id: PropTypes.string,
|
|
||||||
output: PropTypes.string,
|
|
||||||
tests: PropTypes.array,
|
|
||||||
title: PropTypes.string,
|
|
||||||
...reduxFormPropTypes
|
|
||||||
};
|
|
||||||
|
|
||||||
const fields = [ 'solution' ];
|
|
||||||
|
|
||||||
const fieldValidators = {
|
|
||||||
solution: makeRequired(isValidURL)
|
|
||||||
};
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
|
||||||
challengeSelector,
|
|
||||||
outputSelector,
|
|
||||||
testsSelector,
|
|
||||||
(
|
|
||||||
{
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
description
|
|
||||||
},
|
|
||||||
output,
|
|
||||||
tests
|
|
||||||
) => ({
|
|
||||||
id,
|
|
||||||
title,
|
|
||||||
tests,
|
|
||||||
description,
|
|
||||||
output
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const mapDispatchToActions = {
|
|
||||||
executeChallenge
|
|
||||||
};
|
|
||||||
|
|
||||||
export class BackEnd extends PureComponent {
|
|
||||||
renderDescription(description) {
|
|
||||||
if (!Array.isArray(description)) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
return description.map((line, index) => {
|
|
||||||
if (descriptionRegex.test(line)) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{ __html: line }}
|
|
||||||
key={ line.slice(-6) + index }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className='wrappable'
|
|
||||||
dangerouslySetInnerHTML= {{ __html: line }}
|
|
||||||
key={ line.slice(-6) + index }
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
const {
|
|
||||||
description,
|
|
||||||
executeChallenge,
|
|
||||||
output,
|
|
||||||
tests,
|
|
||||||
title,
|
|
||||||
// provided by redux-form
|
|
||||||
fields: { solution },
|
|
||||||
handleSubmit,
|
|
||||||
submitting
|
|
||||||
} = this.props;
|
|
||||||
|
|
||||||
const buttonCopy = submitting ?
|
|
||||||
'Submit and go to my next challenge' :
|
|
||||||
"I've completed this challenge";
|
|
||||||
return (
|
|
||||||
<Row>
|
|
||||||
<Col
|
|
||||||
xs={ 6 }
|
|
||||||
xsOffset={ 3 }
|
|
||||||
>
|
|
||||||
<Row>
|
|
||||||
<ChallengeTitle>
|
|
||||||
{ title }
|
|
||||||
</ChallengeTitle>
|
|
||||||
<ChallengeDescription>
|
|
||||||
{ this.renderDescription(description) }
|
|
||||||
</ChallengeDescription>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<form
|
|
||||||
name='BackEndChallenge'
|
|
||||||
onSubmit={ handleSubmit(executeChallenge) }
|
|
||||||
>
|
|
||||||
<SolutionInput
|
|
||||||
placeholder='https://your-app.com'
|
|
||||||
solution={ solution }
|
|
||||||
/>
|
|
||||||
<Button
|
|
||||||
block={ true }
|
|
||||||
bsStyle='primary'
|
|
||||||
className='btn-big'
|
|
||||||
onClick={ submitting ? null : null }
|
|
||||||
type={ submitting ? null : 'submit' }
|
|
||||||
>
|
|
||||||
{ buttonCopy } (ctrl + enter)
|
|
||||||
</Button>
|
|
||||||
</form>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<br/>
|
|
||||||
<Output
|
|
||||||
defaultOutput={
|
|
||||||
`/**
|
|
||||||
* Test output will go here
|
|
||||||
*/`
|
|
||||||
}
|
|
||||||
output={ output }
|
|
||||||
/>
|
|
||||||
</Row>
|
|
||||||
<Row>
|
|
||||||
<TestSuite tests={ tests } />
|
|
||||||
</Row>
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
BackEnd.displayName = 'BackEnd';
|
|
||||||
BackEnd.propTypes = propTypes;
|
|
||||||
|
|
||||||
export default reduxForm(
|
|
||||||
{
|
|
||||||
form: 'BackEndChallenge',
|
|
||||||
fields,
|
|
||||||
validate: createFormValidator(fieldValidators)
|
|
||||||
},
|
|
||||||
mapStateToProps,
|
|
||||||
mapDispatchToActions
|
|
||||||
)(BackEnd);
|
|
183
packages/learn/src/templates/Challenges/backend/Show.js
Normal file
183
packages/learn/src/templates/Challenges/backend/Show.js
Normal file
@ -0,0 +1,183 @@
|
|||||||
|
/* global graphql */
|
||||||
|
import React, { PureComponent } from 'react';
|
||||||
|
import PropTypes from 'prop-types';
|
||||||
|
import { createSelector } from 'reselect';
|
||||||
|
import { reduxForm } from 'redux-form';
|
||||||
|
import { Col, Row } from 'react-bootstrap';
|
||||||
|
|
||||||
|
import ChallengeTitle from '../components/Challenge-Title';
|
||||||
|
import ChallengeDescription from '../components/Challenge-Description';
|
||||||
|
import TestSuite from '../components/Test-Suite';
|
||||||
|
import Output from '../components/Output';
|
||||||
|
import {
|
||||||
|
executeChallenge,
|
||||||
|
challengeTestsSelector,
|
||||||
|
consoleOutputSelector,
|
||||||
|
initTests,
|
||||||
|
updateChallengeMeta
|
||||||
|
} from '../redux';
|
||||||
|
|
||||||
|
import {
|
||||||
|
createFormValidator,
|
||||||
|
isValidURL,
|
||||||
|
makeRequired,
|
||||||
|
Form
|
||||||
|
} from '../../../components/formHelpers';
|
||||||
|
|
||||||
|
// provided by redux form
|
||||||
|
const reduxFormPropTypes = {
|
||||||
|
fields: PropTypes.object,
|
||||||
|
handleSubmit: PropTypes.func.isRequired,
|
||||||
|
resetForm: PropTypes.func.isRequired,
|
||||||
|
submitting: PropTypes.bool
|
||||||
|
};
|
||||||
|
|
||||||
|
const propTypes = {
|
||||||
|
description: PropTypes.arrayOf(PropTypes.string),
|
||||||
|
executeChallenge: PropTypes.func.isRequired,
|
||||||
|
id: PropTypes.string,
|
||||||
|
output: PropTypes.string,
|
||||||
|
tests: PropTypes.array,
|
||||||
|
title: PropTypes.string,
|
||||||
|
...reduxFormPropTypes
|
||||||
|
};
|
||||||
|
|
||||||
|
const fields = ['solution'];
|
||||||
|
|
||||||
|
const fieldValidators = {
|
||||||
|
solution: makeRequired(isValidURL)
|
||||||
|
};
|
||||||
|
|
||||||
|
const mapStateToProps = createSelector(
|
||||||
|
consoleOutputSelector,
|
||||||
|
challengeTestsSelector,
|
||||||
|
(output, tests) => ({
|
||||||
|
tests,
|
||||||
|
output
|
||||||
|
})
|
||||||
|
);
|
||||||
|
|
||||||
|
const mapDispatchToActions = {
|
||||||
|
executeChallenge,
|
||||||
|
initTests,
|
||||||
|
updateChallengeMeta
|
||||||
|
};
|
||||||
|
|
||||||
|
const formFields = ['solution'];
|
||||||
|
const options = {
|
||||||
|
required: ['solution'],
|
||||||
|
types: {
|
||||||
|
solution: 'url'
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export class BackEnd extends PureComponent {
|
||||||
|
constructor(...props) {
|
||||||
|
super(...props);
|
||||||
|
|
||||||
|
this.handleSubmit = this.handleSubmit.bind(this);
|
||||||
|
}
|
||||||
|
componentDidMount() {
|
||||||
|
const {
|
||||||
|
initTests,
|
||||||
|
updateChallengeMeta,
|
||||||
|
data: { challengeNode: { fields: { tests } } },
|
||||||
|
pathContext: { challengeMeta }
|
||||||
|
} = this.props;
|
||||||
|
initTests(tests);
|
||||||
|
updateChallengeMeta(challengeMeta);
|
||||||
|
}
|
||||||
|
|
||||||
|
componentDidUpdate(prevProps) {
|
||||||
|
const { data: { challengeNode: { title: prevTitle } } } = prevProps;
|
||||||
|
const {
|
||||||
|
initTests,
|
||||||
|
updateChallengeMeta,
|
||||||
|
data: { challengeNode: { title: currentTitle, fields: { tests } } },
|
||||||
|
pathContext: { challengeMeta }
|
||||||
|
} = this.props;
|
||||||
|
if (prevTitle !== currentTitle) {
|
||||||
|
initTests(tests);
|
||||||
|
updateChallengeMeta(challengeMeta);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
handleSubmit(values) {
|
||||||
|
console.log('backend', values);
|
||||||
|
}
|
||||||
|
|
||||||
|
render() {
|
||||||
|
const {
|
||||||
|
data: { challengeNode: { fields: { blockName }, title, description } },
|
||||||
|
output,
|
||||||
|
tests,
|
||||||
|
submitting
|
||||||
|
} = this.props;
|
||||||
|
|
||||||
|
const buttonCopy = submitting
|
||||||
|
? 'Submit and go to my next challenge'
|
||||||
|
: "I've completed this challenge";
|
||||||
|
const blockNameTitle = `${blockName} - ${title}`;
|
||||||
|
return (
|
||||||
|
<Row>
|
||||||
|
<Col xs={6} xsOffset={3}>
|
||||||
|
<Row>
|
||||||
|
<ChallengeTitle>{blockNameTitle}</ChallengeTitle>
|
||||||
|
<ChallengeDescription description={description} />
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<Form
|
||||||
|
buttonText={buttonCopy + '(Ctrl + Enter)'}
|
||||||
|
formFields={formFields}
|
||||||
|
id='backend-form'
|
||||||
|
options={options}
|
||||||
|
submit={this.handleSubmit}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<br />
|
||||||
|
<Output
|
||||||
|
defaultOutput={`/**
|
||||||
|
* Test output will go here
|
||||||
|
*/`}
|
||||||
|
output={output}
|
||||||
|
/>
|
||||||
|
</Row>
|
||||||
|
<Row>
|
||||||
|
<TestSuite tests={tests} />
|
||||||
|
</Row>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
BackEnd.displayName = 'BackEnd';
|
||||||
|
BackEnd.propTypes = propTypes;
|
||||||
|
|
||||||
|
export default reduxForm(
|
||||||
|
{
|
||||||
|
form: 'BackEndChallenge',
|
||||||
|
fields,
|
||||||
|
validate: createFormValidator(fieldValidators)
|
||||||
|
},
|
||||||
|
mapStateToProps,
|
||||||
|
mapDispatchToActions
|
||||||
|
)(BackEnd);
|
||||||
|
|
||||||
|
export const query = graphql`
|
||||||
|
query BackendChallenge($slug: String!) {
|
||||||
|
challengeNode(fields: { slug: { eq: $slug } }) {
|
||||||
|
title
|
||||||
|
guideUrl
|
||||||
|
description
|
||||||
|
challengeType
|
||||||
|
fields {
|
||||||
|
blockName
|
||||||
|
tests {
|
||||||
|
text
|
||||||
|
testString
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
`;
|
@ -1,39 +0,0 @@
|
|||||||
import React from 'react';
|
|
||||||
import { addNS } from 'berkeleys-redux-utils';
|
|
||||||
|
|
||||||
import ChildContainer from '../../Child-Container.jsx';
|
|
||||||
import BackEnd from './Back-End.jsx';
|
|
||||||
import { types } from '../../redux';
|
|
||||||
import Panes from '../../../../Panes';
|
|
||||||
import _Map from '../../../../Map';
|
|
||||||
|
|
||||||
const propTypes = {};
|
|
||||||
|
|
||||||
export const mapStateToPanes = addNS(
|
|
||||||
'backend',
|
|
||||||
() => ({
|
|
||||||
[types.toggleMap]: 'Map',
|
|
||||||
[types.toggleMain]: 'Main'
|
|
||||||
})
|
|
||||||
);
|
|
||||||
|
|
||||||
const nameToComponent = {
|
|
||||||
Map: _Map,
|
|
||||||
Main: BackEnd
|
|
||||||
};
|
|
||||||
|
|
||||||
const renderPane = name => {
|
|
||||||
const Comp = nameToComponent[name];
|
|
||||||
return Comp ? <Comp /> : <span>Pane { name } not found</span>;
|
|
||||||
};
|
|
||||||
|
|
||||||
export default function ShowBackEnd() {
|
|
||||||
return (
|
|
||||||
<ChildContainer isFullWidth={ true }>
|
|
||||||
<Panes render={ renderPane } />
|
|
||||||
</ChildContainer>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
ShowBackEnd.displayName = 'ShowBackEnd';
|
|
||||||
ShowBackEnd.propTypes = propTypes;
|
|
@ -1 +0,0 @@
|
|||||||
export { default, mapStateToPanes } from './Show.jsx';
|
|
@ -1,17 +1,41 @@
|
|||||||
import React from 'react';
|
import React from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { Col, Row } from 'react-bootstrap';
|
import { Col, Row } from 'react-bootstrap';
|
||||||
|
import { descriptionRegex } from '../../../../utils';
|
||||||
|
|
||||||
const propTypes = {
|
const propTypes = {
|
||||||
children: PropTypes.array
|
description: PropTypes.arrayOf(PropTypes.string)
|
||||||
};
|
};
|
||||||
|
|
||||||
function ChallengeDescription({ children }) {
|
function renderDescription(description) {
|
||||||
|
if (!Array.isArray(description)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return description.map((line, index) => {
|
||||||
|
if (descriptionRegex.test(line)) {
|
||||||
|
return (
|
||||||
|
<div
|
||||||
|
dangerouslySetInnerHTML={{ __html: line }}
|
||||||
|
key={line.slice(-6) + index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<p
|
||||||
|
className='wrappable'
|
||||||
|
dangerouslySetInnerHTML={{ __html: line }}
|
||||||
|
key={line.slice(-6) + index}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
function ChallengeDescription({ description }) {
|
||||||
// TODO: Remove bootstrap
|
// TODO: Remove bootstrap
|
||||||
return (
|
return (
|
||||||
<Row>
|
<Row>
|
||||||
<Col className='challenge-instructions' xs={12}>
|
<Col className='challenge-instructions' xs={12}>
|
||||||
{children}
|
{renderDescription(description)}
|
||||||
</Col>
|
</Col>
|
||||||
</Row>
|
</Row>
|
||||||
);
|
);
|
||||||
|
@ -19,7 +19,6 @@ import {
|
|||||||
executeChallenge,
|
executeChallenge,
|
||||||
initConsole
|
initConsole
|
||||||
} from '../redux';
|
} from '../redux';
|
||||||
import { descriptionRegex } from '../../../../utils';
|
|
||||||
|
|
||||||
const mapStateToProps = createSelector(
|
const mapStateToProps = createSelector(
|
||||||
consoleOutputSelector,
|
consoleOutputSelector,
|
||||||
@ -70,26 +69,6 @@ export class SidePanel extends PureComponent {
|
|||||||
this.descriptionTop = node;
|
this.descriptionTop = node;
|
||||||
}
|
}
|
||||||
|
|
||||||
renderDescription(description = ['Happy Coding!']) {
|
|
||||||
return description.map((line, index) => {
|
|
||||||
if (descriptionRegex.test(line)) {
|
|
||||||
return (
|
|
||||||
<div
|
|
||||||
dangerouslySetInnerHTML={{ __html: line }}
|
|
||||||
key={line.slice(-6) + index}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<p
|
|
||||||
className='wrappable'
|
|
||||||
dangerouslySetInnerHTML={{ __html: line }}
|
|
||||||
key={line.slice(-6) + index}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
render() {
|
||||||
const {
|
const {
|
||||||
title,
|
title,
|
||||||
@ -104,9 +83,7 @@ export class SidePanel extends PureComponent {
|
|||||||
<div ref={this.bindTopDiv} />
|
<div ref={this.bindTopDiv} />
|
||||||
<div>
|
<div>
|
||||||
<ChallengeTitle>{title}</ChallengeTitle>
|
<ChallengeTitle>{title}</ChallengeTitle>
|
||||||
<ChallengeDescription>
|
<ChallengeDescription description={description} />
|
||||||
{this.renderDescription(description)}
|
|
||||||
</ChallengeDescription>
|
|
||||||
</div>
|
</div>
|
||||||
<ToolPanel executeChallenge={executeChallenge} guideUrl={guideUrl} />
|
<ToolPanel executeChallenge={executeChallenge} guideUrl={guideUrl} />
|
||||||
<Spacer />
|
<Spacer />
|
||||||
|
@ -1,6 +1,5 @@
|
|||||||
import React, { PureComponent } from 'react';
|
import React, { PureComponent } from 'react';
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types';
|
||||||
import { reduxForm } from 'redux-form';
|
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Form,
|
Form,
|
||||||
@ -61,8 +60,4 @@ export class ProjectForm extends PureComponent {
|
|||||||
|
|
||||||
ProjectForm.propTypes = propTypes;
|
ProjectForm.propTypes = propTypes;
|
||||||
|
|
||||||
export default reduxForm({
|
export default ProjectForm;
|
||||||
form: 'NewFrontEndProject',
|
|
||||||
fields: frontEndFields,
|
|
||||||
validate: createFormValidator(fieldValidators)
|
|
||||||
})(ProjectForm);
|
|
||||||
|
@ -3,6 +3,7 @@ exports.dasherize = function dasherize(name) {
|
|||||||
.toLowerCase()
|
.toLowerCase()
|
||||||
.replace(/\s/g, '-')
|
.replace(/\s/g, '-')
|
||||||
.replace(/[^a-z0-9\-\.]/gi, '')
|
.replace(/[^a-z0-9\-\.]/gi, '')
|
||||||
|
.replace(/\./g, '-')
|
||||||
.replace(/\:/g, '');
|
.replace(/\:/g, '');
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user