diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/announce-new-users.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/announce-new-users.md index 5c2e33a0aa8..b8b02493261 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/announce-new-users.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/announce-new-users.md @@ -8,13 +8,13 @@ dashedName: announce-new-users # --description-- -Many chat rooms are able to announce when a user connects or disconnects and then display that to all of the connected users in the chat. Seeing as though you already are emitting an event on connect and disconnect, you will just have to modify this event to support such a feature. The most logical way of doing so is sending 3 pieces of data with the event: the name of the user who connected/disconnected, the current user count, and if that name connected or disconnected. +Many chat rooms are able to announce when a user connects or disconnects and then display that to all of the connected users in the chat. Seeing as though you already are emitting an event on connect and disconnect, you will just have to modify this event to support such a feature. The most logical way of doing so is sending 3 pieces of data with the event: the username of the user who connected/disconnected, the current user count, and if that username connected or disconnected. -Change the event name to `'user'`, and pass an object along containing the fields 'name', 'currentUsers', and 'connected' (to be `true` in case of connection, or `false` for disconnection of the user sent). Be sure to change both 'user count' events and set the disconnect one to send `false` for the field 'connected' instead of `true` like the event emitted on connect. +Change the event name to `'user'`, and pass an object along containing the fields `username`, `currentUsers`, and `connected` (to be `true` in case of connection, or `false` for disconnection of the user sent). Be sure to change both `'user count'` events and set the disconnect one to send `false` for the field `connected` instead of `true` like the event emitted on connect. ```js io.emit('user', { - name: socket.request.user.name, + username: socket.request.user.username, currentUsers, connected: true }); @@ -28,55 +28,49 @@ An implementation of this could look like the following: socket.on('user', data => { $('#num-users').text(data.currentUsers + ' users online'); let message = - data.name + + data.username + (data.connected ? ' has joined the chat.' : ' has left the chat.'); $('#messages').append($('
"|')GITHUB_CLIENT_SECRET\k\])/g, - 'You should use process.env.GITHUB_CLIENT_SECRET' - ); - assert.match( - data, - /process\.env(\.GITHUB_CLIENT_ID|\[(?"|')GITHUB_CLIENT_ID\k\])/g, - 'You should use process.env.GITHUB_CLIENT_ID' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/auth.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /passport\.use.*new GitHubStrategy/gis, + 'Passport should use a new GitHubStrategy' ); + assert.match( + data, + /callbackURL:\s*("|').*("|')/gi, + 'You should have a callbackURL' + ); + assert.match( + data, + /process\.env(\.GITHUB_CLIENT_SECRET|\[(?"|')GITHUB_CLIENT_SECRET\k\])/g, + 'You should use process.env.GITHUB_CLIENT_SECRET' + ); + assert.match( + data, + /process\.env(\.GITHUB_CLIENT_ID|\[(?"|')GITHUB_CLIENT_ID\k\])/g, + 'You should use process.env.GITHUB_CLIENT_ID' + ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md index 87feb7dfca3..aef68751990 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication-iii.md @@ -16,6 +16,7 @@ myDataBase.findOneAndUpdate( { $setOnInsert: { id: profile.id, + username: profile.username, name: profile.displayName || 'John Doe', photo: profile.photos[0].value || '', email: Array.isArray(profile.emails) @@ -40,33 +41,30 @@ myDataBase.findOneAndUpdate( `findOneAndUpdate` allows you to search for an object and update it. If the object doesn't exist, it will be inserted and made available to the callback function. In this example, we always set `last_login`, increment the `login_count` by `1`, and only populate the majority of the fields when a new object (new user) is inserted. Notice the use of default values. Sometimes a profile returned won't have all the information filled out or the user will keep it private. In this case, you handle it to prevent an error. -You should be able to login to your app now--try it! +You should be able to login to your app now. Try it! -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- GitHub strategy setup should be complete. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/auth.js').then( - (data) => { - assert.match( - data, - /GitHubStrategy[^]*myDataBase/gi, - 'Strategy should use now use the database to search for the user' - ); - assert.match( - data, - /GitHubStrategy[^]*return cb/gi, - 'Strategy should return the callback function "cb"' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/auth.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /GitHubStrategy[^]*myDataBase/gi, + 'Strategy should use now use the database to search for the user' ); + assert.match( + data, + /GitHubStrategy[^]*return cb/gi, + 'Strategy should return the callback function "cb"' + ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication.md index a744f418598..8695f8063b2 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/implementation-of-social-authentication.md @@ -10,19 +10,21 @@ dashedName: implementation-of-social-authentication The basic path this kind of authentication will follow in your app is: -1. User clicks a button or link sending them to our route to authenticate using a specific strategy (e.g. GitHub). +1. User clicks a button or link sending them to your route to authenticate using a specific strategy (e.g. GitHub). 2. Your route calls `passport.authenticate('github')` which redirects them to GitHub. -3. The page the user lands on, on GitHub, allows them to login if they aren't already. It then asks them to approve access to their profile from our app. -4. The user is then returned to our app at a specific callback url with their profile if they are approved. +3. The page the user lands on, on GitHub, allows them to login if they aren't already. It then asks them to approve access to their profile from your app. +4. The user is then returned to your app at a specific callback url with their profile if they are approved. 5. They are now authenticated, and your app should check if it is a returning profile, or save it in your database if it is not. -Strategies with OAuth require you to have at least a *Client ID* and a *Client Secret* which is a way for the service to verify who the authentication request is coming from and if it is valid. These are obtained from the site you are trying to implement authentication with, such as GitHub, and are unique to your app--**THEY ARE NOT TO BE SHARED** and should never be uploaded to a public repository or written directly in your code. A common practice is to put them in your `.env` file and reference them like so: `process.env.GITHUB_CLIENT_ID`. For this challenge we're going to use the GitHub strategy. +Strategies with OAuth require you to have at least a *Client ID* and a *Client Secret* which is a way for the service to verify who the authentication request is coming from and if it is valid. These are obtained from the site you are trying to implement authentication with, such as GitHub, and are unique to your app--**THEY ARE NOT TO BE SHARED** and should never be uploaded to a public repository or written directly in your code. A common practice is to put them in your `.env` file and reference them like so: `process.env.GITHUB_CLIENT_ID`. For this challenge you are going to use the GitHub strategy. -Obtaining your *Client ID and Secret* from GitHub is done in your account profile settings under 'developer settings', then 'OAuth applications'. Click 'Register a new application', name your app, paste in the url to your Replit homepage (**Not the project code's url**), and lastly, for the callback url, paste in the same url as the homepage but with `/auth/github/callback` added on. This is where users will be redirected for us to handle after authenticating on GitHub. Save the returned information as `'GITHUB_CLIENT_ID'` and `'GITHUB_CLIENT_SECRET'` in your `.env` file. +Follow these instructions to obtain your *Client ID and Secret* from GitHub. Go to your GitHub profile settings and click 'developer settings', then 'OAuth Apps'. Click 'New OAuth App', then give your app a name, paste in the URL to your Replit homepage (**Not the project code's url**) and, for the callback URL, paste in the same URL as the homepage but add `/auth/github/callback` to the end of it. This is where users will be redirected after authenticating on GitHub. After you do all that, click 'Register application'. -In your `routes.js` file, add `showSocialAuth: true` to the homepage route, after `showRegistration: true`. Now, create 2 routes accepting GET requests: `/auth/github` and `/auth/github/callback`. The first should only call passport to authenticate `'github'`. The second should call passport to authenticate `'github'` with a failure redirect to `/`, and then if that is successful redirect to `/profile` (similar to our last project). +On the next page, click 'Generate a new client secret' to create a new client secret. Save the client ID and your client secret in your project's `.env` file as `GITHUB_CLIENT_ID` and `GITHUB_CLIENT_SECRET`. -An example of how `/auth/github/callback` should look is similar to how we handled a normal login: +In your `routes.js` file, add `showSocialAuth: true` to the homepage route, after `showRegistration: true`. Now, create 2 routes accepting GET requests: `/auth/github` and `/auth/github/callback`. The first should only call passport to authenticate `'github'`. The second should call passport to authenticate `'github'` with a failure redirect to `/`, and then if that is successful redirect to `/profile` (similar to your last project). + +An example of how `/auth/github/callback` should look is similar to how you handled a normal login: ```js app.route('/login') @@ -31,7 +33,7 @@ app.route('/login') }); ``` -Submit your page when you think you've got it right. If you're running into errors, you can check out the project up to this point https://gist.github.com/camperbot/1f7f6f76adb178680246989612bea21e. +Submit your page when you think you've got it right. If you're running into errors, you can check out the project up to this point. # --hints-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/logging-a-user-out.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/logging-a-user-out.md index 69686500bf0..8deeb24fc70 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/logging-a-user-out.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/logging-a-user-out.md @@ -8,9 +8,9 @@ dashedName: logging-a-user-out # --description-- -Creating the logout logic is easy. The route should just unauthenticate the user and redirect to the home page instead of rendering any view. +Creating the logout logic is easy. The route should just unauthenticate the user, and redirect to the home page instead of rendering any view. -In passport, unauthenticating a user is as easy as just calling `req.logout();` before redirecting. +In passport, unauthenticating a user is as easy as just calling `req.logout()` before redirecting. Add this `/logout` route to do that: ```js app.route('/logout') @@ -20,7 +20,7 @@ app.route('/logout') }); ``` -You may have noticed that we're not handling missing pages (404). The common way to handle this in Node is with the following middleware. Go ahead and add this in after all your other routes: +You may have noticed that you are not handling missing pages (404). The common way to handle this in Node is with the following middleware. Go ahead and add this in after all your other routes: ```js app.use((req, res, next) => { @@ -30,44 +30,38 @@ app.use((req, res, next) => { }); ``` -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- `req.logout()` should be called in your `/logout` route. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /req.logout/gi, - 'You should be calling req.logout() in your /logout route' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /req.logout/gi, + 'You should be calling req.logout() in your /logout route' ); +} ``` -Logout should redirect to the home page. +`/logout` should redirect to the home page. ```js -(getUserInput) => - $.get(getUserInput('url') + '/logout').then( - (data) => { - assert.match( - data, - /Home page/gi, - 'When a user logs out they should be redirected to the homepage' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/logout", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /Home page/gi, + 'When a user logs out they should be redirected to the homepage' ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/registration-of-new-users.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/registration-of-new-users.md index 2b64f9d1b5d..6e39bdee774 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/registration-of-new-users.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/registration-of-new-users.md @@ -8,16 +8,25 @@ dashedName: registration-of-new-users # --description-- -Now we need to allow a new user on our site to register an account. On the `res.render` for the home page add a new variable to the object passed along--`showRegistration: true`. When you refresh your page, you should then see the registration form that was already created in your `index.pug` file! This form is set up to **POST** on `/register`, so this is where we should set up to accept the **POST** and create the user object in the database. +Now you need to allow a new user on your site to register an account. In the `res.render` for the home page add a new variable to the object passed along - `showRegistration: true`. When you refresh your page, you should then see the registration form that was already created in your `index.pug` file. This form is set up to **POST** on `/register`, so create that route and have it add the user object to the database by following the logic below. -The logic of the registration route should be as follows: Register the new user > Authenticate the new user > Redirect to /profile +The logic of the registration route should be as follows: -The logic of step 1, registering the new user, should be as follows: Query database with a findOne command > if user is returned then it exists and redirect back to home *OR* if user is undefined and no error occurs then 'insertOne' into the database with the username and password, and, as long as no errors occur, call *next* to go to step 2, authenticating the new user, which we've already written the logic for in our POST */login* route. +1. Register the new user +2. Authenticate the new user +3. Redirect to `/profile` + +The logic of step 1 should be as follows: + +1. Query database with `findOne` +2. If there is an error, call `next` with the error +3. If a user is returned, redirect back to home +4. If a user is not found and no errors occur, then `insertOne` into the database with the username and password. As long as no errors occur there, call `next` to go to step 2, authenticating the new user, which you already wrote the logic for in your `POST /login` route. ```js app.route('/register') .post((req, res, next) => { - myDataBase.findOne({ username: req.body.username }, function(err, user) { + myDataBase.findOne({ username: req.body.username }, (err, user) => { if (err) { next(err); } else if (user) { @@ -47,33 +56,30 @@ app.route('/register') ); ``` -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. **NOTE:** From this point onwards, issues can arise relating to the use of the *picture-in-picture* browser. If you are using an online IDE which offers a preview of the app within the editor, it is recommended to open this preview in a new tab. # --hints-- -You should register route and display on home. +You should have a `/register` route and display a registration form on the home page. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /showRegistration:( |)true/gi, - 'You should be passing the variable showRegistration as true to your render function for the homepage' - ); - assert.match( - data, - /register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi, - 'You should have a route accepted a post request on register that querys the db with findone and the query being username: req.body.username' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /showRegistration:( |)true/gi, + 'You should be passing the variable showRegistration as true to your render function for the homepage' ); + assert.match( + data, + /register[^]*post[^]*findOne[^]*username:( |)req.body.username/gi, + 'You should have a route that accepts a POST request on /register that queries the db with findOne and the query being username: req.body.username' + ); +} ``` Registering should work. diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.md index ab19e7387f2..3698414dada 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/send-and-display-chat-messages.md @@ -22,50 +22,44 @@ Within the form submit code, you should emit an event after you define `messageT socket.emit('chat message', messageToSend); ``` -Now, on your server, you should be listening to the socket for the event `'chat message'` with the data being named `message`. Once the event is received, it should emit the event `'chat message'` to all sockets `io.emit` with the data being an object containing `name` and `message`. +Now, on your server, you should be listening to the socket for the event `'chat message'` with the data being named `message`. Once the event is received, it should emit the event `'chat message'` to all sockets using `io.emit`, sending a data object containing the `username` and `message`. -In `client.js`, you should now listen for event `'chat message'` and, when received, append a list item to `#messages` with the name, a colon, and the message! +In `client.js`, you should now listen for event `'chat message'` and, when received, append a list item to `#messages` with the username, a colon, and the message! At this point, the chat should be fully functional and sending messages across all clients! -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- Server should listen for `'chat message'` and then emit it properly. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*name.*message/gis, - 'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /socket.on.*('|")chat message('|")[^]*io.emit.*('|")chat message('|").*username.*message/s, + 'Your server should listen to the socket for "chat message" then emit to all users "chat message" with name and message in the data object' ); +} ``` Client should properly handle and display the new data from event `'chat message'`. ```js -(getUserInput) => - $.get(getUserInput('url') + '/public/client.js').then( - (data) => { - assert.match( - data, - /socket.on.*('|")chat message('|")[^]*messages.*li/gis, - 'You should append a list item to #messages on your client within the "chat message" event listener to display the new message' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/public/client.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /socket.on.*('|")chat message('|")[^]*messages.*li/s, + 'You should append a list item to #messages on your client within the "chat message" event listener to display the new message' ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/serialization-of-a-user-object.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/serialization-of-a-user-object.md index eb99213c0b8..49bc4ab4e37 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/serialization-of-a-user-object.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/serialization-of-a-user-object.md @@ -10,11 +10,23 @@ dashedName: serialization-of-a-user-object Serialization and deserialization are important concepts in regards to authentication. To serialize an object means to convert its contents into a small *key* that can then be deserialized into the original object. This is what allows us to know who has communicated with the server without having to send the authentication data, like the username and password, at each request for a new page. -To set this up properly, we need to have a serialize function and a deserialize function. In Passport, we create these with `passport.serializeUser( OURFUNCTION )` and `passport.deserializeUser( OURFUNCTION )` +To set this up properly, you need to have a serialize function and a deserialize function. In Passport, these can be created with: -The `serializeUser` is called with 2 arguments, the full user object and a callback used by passport. A unique key to identify that user should be returned in the callback, the easiest one to use being the user's `_id` in the object. It should be unique as it is generated by MongoDB. Similarly, `deserializeUser` is called with that key and a callback function for passport as well, but, this time, we have to take that key and return the full user object to the callback. To make a query search for a Mongo `_id`, you will have to create `const ObjectID = require('mongodb').ObjectID;`, and then to use it you call `new ObjectID(THE_ID)`. `mongodb@~3.6.0` has already been added as a dependency. You can see this in the examples below: +```javascript +passport.serializeUser(cb); +passport.deserializeUser(cb); +``` -```js +The callback function passed to `serializeUser` is called with two arguments: the full user object, and a callback used by passport. + +The callback expects two arguments: An error, if any, and a unique key to identify the user that should be returned in the callback. You will use the user's `_id` in the object. This is guaranteed to be unique, as it is generated by MongoDB. + +Similarly, `deserializeUser` is called with two arguments: the unique key, and a callback function. + +This callback expects two arguments: An error, if any, and the full user object. To get the full user object, make a query search for a Mongo `_id`, as shown below: + + +```javascript passport.serializeUser((user, done) => { done(null, user._id); }); @@ -26,98 +38,91 @@ passport.deserializeUser((id, done) => { }); ``` -NOTE: This `deserializeUser` will throw an error until we set up the DB in the next step, so for now comment out the whole block and just call `done(null, null)` in the function `deserializeUser`. +Add the two functions above to your server. The `ObjectID` class comes from the `mongodb` package. `mongodb@~3.6.0` has already been added as a dependency. Declare this class with: -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +```javascript +const { ObjectID } = require('mongodb'); +``` + +The `deserializeUser` will throw an error until you set up the database connection. So, for now, comment out the `myDatabase.findOne` call, and just call `done(null, null)` in the `deserializeUser` callback function. + +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- You should serialize user function correctly. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /passport.serializeUser/gi, - 'You should have created your passport.serializeUser function' - ); - assert.match( - data, - /null,\s*user._id/gi, - 'There should be a callback in your serializeUser with (null, user._id)' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /passport.serializeUser/gi, + 'You should have created your passport.serializeUser function' ); + assert.match( + data, + /null,\s*user._id/gi, + 'There should be a callback in your serializeUser with (null, user._id)' + ); +} ``` You should deserialize user function correctly. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /passport.deserializeUser/gi, - 'You should have created your passport.deserializeUser function' - ); - assert.match( - data, - /null,\s*null/gi, - 'There should be a callback in your deserializeUser with (null, null) for now' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /passport.deserializeUser/gi, + 'You should have created your passport.deserializeUser function' ); + assert.match( + data, + /null,\s*null/gi, + 'There should be a callback in your deserializeUser with (null, null) for now' + ); +} ``` MongoDB should be a dependency. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/package.json').then( - (data) => { - var packJson = JSON.parse(data); - assert.property( - packJson.dependencies, - 'mongodb', - 'Your project should list "mongodb" as a dependency' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/package.json", getUserInput("url")); + const res = await fetch(url); + const packJson = await res.json(); + assert.property( + packJson.dependencies, + 'mongodb', + 'Your project should list "mongodb" as a dependency' ); +} ``` Mongodb should be properly required including the ObjectId. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /require.*("|')mongodb\1/gi, - 'You should have required mongodb' - ); - assert.match( - data, - /new ObjectID.*id/gi, - 'Even though the block is commented out, you should use new ObjectID(id) for when we add the database' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /require.*("|')mongodb\1/gi, + 'You should have required mongodb' ); + assert.match( + data, + /new ObjectID.*id/gi, + 'Even though the block is commented out, you should use new ObjectID(id) for when we add the database' + ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-a-template-engine.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-a-template-engine.md index 08d21a01237..c317ca798aa 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-a-template-engine.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-a-template-engine.md @@ -20,89 +20,87 @@ A template engine enables you to use static template files (such as those writte `pug@~3.0.0` has already been installed, and is listed as a dependency in your `package.json` file. -Express needs to know which template engine you are using. We will use the `set` method to assign `pug` as the `view engine` property's value: `app.set('view engine', 'pug')` +Express needs to know which template engine you are using. Use the `set` method to assign `pug` as the `view engine` property's value: -Your page will be blank until you correctly render the index file in the `views/pug` directory. +```javascript +app.set('view engine', 'pug'); +``` -To render the `pug` template, you need to use `res.render()` in the `/` route. Pass the file path to the `views/pug` directory as the argument to the method. The path can be a relative path (relative to views), or an absolute path, and does not require a file extension. +After that, add another `set` method that sets the `views` property of your `app` to point to the `./views/pug` directory. This tells Express to render all views relative to that directory. -If all went as planned, your app home page will no longer be blank and will display a message indicating you've successfully rendered the Pug template! +Finally, use `res.render()` in the route for your home page, passing `index` as the first argument. This will render the `pug` template. -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +If all went as planned, your app home page will no longer be blank. Instead, it will display a message indicating you've successfully rendered the Pug template! + +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- Pug should be a dependency. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/package.json').then( - (data) => { - var packJson = JSON.parse(data); - assert.property( - packJson.dependencies, - 'pug', - 'Your project should list "pug" as a dependency' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/package.json", getUserInput("url")); + const res = await fetch(url); + const packJson = await res.json(); + assert.property( + packJson.dependencies, + 'pug', + 'Your project should list "pug" as a dependency' ); +} ``` View engine should be Pug. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /('|")view engine('|"),( |)('|")pug('|")/gi, - 'Your project should set Pug as a view engine' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } - ); +async (getUserInput) => { + const url = new URL("/_api/app", getUserInput("url")); + const res = await fetch(url); + const app = await res.json(); + assert.equal(app?.settings?.['view engine'], "pug"); +} +``` + +You should set the `views` property of the application to `./views/pug`. + +```js +async (getUserInput) => { + const url = new URL("/_api/app", getUserInput("url")); + const res = await fetch(url); + const app = await res.json(); + assert.equal(app?.settings?.views, "./views/pug"); +} ``` Use the correct ExpressJS method to render the index page from the response. ```js -(getUserInput) => - $.get(getUserInput('url') + '/').then( - (data) => { +async (getUserInput) => { + const url = new URL("/", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); assert.match( data, /FCC Advanced Node and Express/gi, 'You successfully rendered the Pug template!' ); - }, - (xhr) => { - throw new Error(xhr.statusText); } - ); ``` Pug should be working. ```js -(getUserInput) => - $.get(getUserInput('url') + '/').then( - (data) => { +async (getUserInput) => { + const url = new URL("/", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); assert.match( data, /pug-success-message/gi, 'Your projects home page should now be rendered by pug with the projects .pug file unaltered' ); - }, - (xhr) => { - throw new Error(xhr.statusText); } - ); ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-passport.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-passport.md index 52d64d22cdc..1255e29c5e9 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-passport.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-passport.md @@ -8,15 +8,15 @@ dashedName: set-up-passport # --description-- -It's time to set up *Passport* so we can finally start allowing a user to register or login to an account! In addition to Passport, we will use Express-session to handle sessions. Express-session has a ton of advanced features you can use, but for now we're just going to use the basics! Using this middleware saves the session id as a cookie in the client and allows us to access the session data using that id on the server. This way we keep personal account information out of the cookie used by the client to verify to our server they are authenticated and just keep the *key* to access the data stored on the server. +It's time to set up *Passport* so you can finally start allowing a user to register or log in to an account. In addition to Passport, you will use Express-session to handle sessions. Express-session has a ton of advanced features you can use, but for now you are just going to use the basics. Using this middleware saves the session id as a cookie in the client, and allows us to access the session data using that id on the server. This way, you keep personal account information out of the cookie used by the client to tell to your server clients are authenticated and keep the *key* to access the data stored on the server. `passport@~0.4.1` and `express-session@~1.17.1` are already installed, and are both listed as dependencies in your `package.json` file. -You will need to set up the session settings now and initialize Passport. Be sure to first create the variables 'session' and 'passport' to require 'express-session' and 'passport' respectively. +You will need to set up the session settings and initialize Passport. First, create the variables `session` and `passport` to require `express-session` and `passport` respectively. -To set up your express app to use the session we'll define just a few basic options. Be sure to add 'SESSION_SECRET' to your .env file and give it a random value. This is used to compute the hash used to encrypt your cookie! +Then, set up your Express app to use the session by defining the following options: -```js +```javascript app.use(session({ secret: process.env.SESSION_SECRET, resave: true, @@ -25,98 +25,79 @@ app.use(session({ })); ``` -As well you can go ahead and tell your express app to **use** 'passport.initialize()' and 'passport.session()'. (For example, `app.use(passport.initialize());`) +Be sure to add `SESSION_SECRET` to your `.env` file, and give it a random value. This is used to compute the hash used to encrypt your cookie! -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +After you do all that, tell your express app to **use** `passport.initialize()` and `passport.session()`. + +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- Passport and Express-session should be dependencies. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/package.json').then( - (data) => { - var packJson = JSON.parse(data); - assert.property( - packJson.dependencies, - 'passport', - 'Your project should list "passport" as a dependency' - ); - assert.property( - packJson.dependencies, - 'express-session', - 'Your project should list "express-session" as a dependency' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/package.json", getUserInput("url")); + const res = await fetch(url); + const packJson = await res.json(); + assert.property( + packJson.dependencies, + 'passport', + 'Your project should list "passport" as a dependency' ); + assert.property( + packJson.dependencies, + 'express-session', + 'Your project should list "express-session" as a dependency' + ); +} ``` Dependencies should be correctly required. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /require.*("|')passport("|')/gi, - 'You should have required passport' - ); - assert.match( - data, - /require.*("|')express-session("|')/gi, - 'You should have required express-session' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /require.*("|')passport("|')/, + 'You should have required passport' ); + assert.match( + data, + /require.*("|')express-session("|')/, + 'You should have required express-session' + ); +} ``` Express app should use new dependencies. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /passport.initialize/gi, - 'Your express app should use "passport.initialize()"' - ); - assert.match( - data, - /passport.session/gi, - 'Your express app should use "passport.session()"' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } - ); +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match(data, /passport\.initialize/, 'Your express app should use "passport.initialize()"'); + assert.match(data, /passport\.session/, 'Your express app should use "passport.session()"'); +} ``` Session and session secret should be correctly set up. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /secret *: *process\.env(\.SESSION_SECRET|\[(?"|')SESSION_SECRET\k\])/g, - 'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /secret *:\s*process\.env(\.SESSION_SECRET|\[(?"|')SESSION_SECRET\k\])/, + 'Your express app should have express-session set up with your secret as process.env.SESSION_SECRET' ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-the-environment.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-the-environment.md index 853b0779305..a1db53551b9 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-the-environment.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/set-up-the-environment.md @@ -19,7 +19,7 @@ const io = require('socket.io')(http); Now that the *http* server is mounted on the *express app*, you need to listen from the *http* server. Change the line with `app.listen` to `http.listen`. -The first thing needing to be handled is listening for a new connection from the client. The on keyword does just that- listen for a specific event. It requires 2 arguments: a string containing the title of the event that's emitted, and a function with which the data is passed through. In the case of our connection listener, we use *socket* to define the data in the second argument. A socket is an individual client who is connected. +The first thing needing to be handled is listening for a new connection from the client. The on keyword does just that- listen for a specific event. It requires 2 arguments: a string containing the title of the event that's emitted, and a function with which the data is passed through. In the case of our connection listener, use `socket` to define the data in the second argument. A socket is an individual client who is connected. To listen for connections to your server, add the following within your database connection: @@ -36,105 +36,89 @@ Now for the client to connect, you just need to add the following to your `clien let socket = io(); ``` -The comment suppresses the error you would normally see since 'io' is not defined in the file. We've already added a reliable CDN to the Socket.IO library on the page in chat.pug. +The comment suppresses the error you would normally see since 'io' is not defined in the file. You have already added a reliable CDN to the Socket.IO library on the page in `chat.pug`. -Now try loading up your app and authenticate and you should see in your server console 'A user has connected'! +Now try loading up your app and authenticate and you should see in your server console `A user has connected`. **Note:**`io()` works only when connecting to a socket hosted on the same url/server. For connecting to an external socket hosted elsewhere, you would use `io.connect('URL');`. -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- `socket.io` should be a dependency. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/package.json').then( - (data) => { - var packJson = JSON.parse(data); - assert.property( - packJson.dependencies, - 'socket.io', - 'Your project should list "socket.io" as a dependency' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/package.json", getUserInput("url")); + const res = await fetch(url); + const packJson = await res.json(); + assert.property( + packJson.dependencies, + 'socket.io', + 'Your project should list "socket.io" as a dependency' ); +} ``` You should correctly require and instantiate `http` as `http`. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /http.*=.*require.*('|")http\1/gi, - 'Your project should list "http" as a dependency' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /http.*=.*require.*('|")http\1/s, + 'Your project should list "http" as a dependency' ); +} ``` You should correctly require and instantiate `socket.io` as `io`. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /io.*=.*require.*('|")socket.io\1.*http/gi, - 'You should correctly require and instantiate socket.io as io.' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /io.*=.*require.*('|")socket.io\1.*http/s, + 'You should correctly require and instantiate socket.io as io.' ); +} ``` Socket.IO should be listening for connections. ```js -(getUserInput) => - $.get(getUserInput('url') + '/_api/server.js').then( - (data) => { - assert.match( - data, - /io.on.*('|")connection\1.*socket/gi, - 'io should listen for "connection" and socket should be the 2nd arguments variable' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/_api/server.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /io.on.*('|")connection\1.*socket/s, + 'io should listen for "connection" and socket should be the 2nd arguments variable' ); +} ``` Your client should connect to your server. ```js -(getUserInput) => - $.get(getUserInput('url') + '/public/client.js').then( - (data) => { - assert.match( - data, - /socket.*=.*io/gi, - 'Your client should be connection to server with the connection defined as socket' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/public/client.js", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /socket.*=.*io/s, + 'Your client should be connection to server with the connection defined as socket' ); +} ``` # --solutions-- diff --git a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/use-a-template-engines-powers.md b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/use-a-template-engines-powers.md index f956f5ea5ec..17ac6889af9 100644 --- a/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/use-a-template-engines-powers.md +++ b/curriculum/challenges/english/06-quality-assurance/advanced-node-and-express/use-a-template-engines-powers.md @@ -12,58 +12,63 @@ One of the greatest features of using a template engine is being able to pass va In your Pug file, you're able to use a variable by referencing the variable name as `#{variable_name}` inline with other text on an element or by using an equal sign on the element without a space such as `p=variable_name` which assigns the variable's value to the p element's text. - Pug is all about using whitespace and tabs to show nested elements and cutting down on the amount of code needed to make a beautiful site. Read the Pug documentation for more information on usage and syntax. +Pug is all about using whitespace and tabs to show nested elements and cutting down on the amount of code needed to make a beautiful site. + +Take the following Pug code for example: - Here is an example: - - ```html - - head - script(type='text/javascript'). - if (foo) bar(1 + 5); - body - if youAreUsingPug - p You are amazing - else - p Get on it! - - - - - - -You are amazing
- - ``` +```pug +head + script(type='text/javascript'). + if (foo) bar(1 + 5); +body + if youAreUsingPug + p You are amazing + else + p Get on it! +``` -Looking at our pug file `index.pug` included in your project, we used the variables `title` and `message`. +The above yields the following HTML: -To pass those along from our server, you will need to add an object as a second argument to your `res.render` with the variables and their values. For example, pass this object along setting the variables for your index view: `{title: 'Hello', message: 'Please login'}` +```html + + + + +You are amazing
+ +``` -It should look like: `res.render(process.cwd() + '/views/pug/index', {title: 'Hello', message: 'Please login'});` Now refresh your page and you should see those values rendered in your view in the correct spot as laid out in your `index.pug` file! +Your `index.pug` file included in your project, uses the variables `title` and `message`. -Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. +Pass those from your server to the Pug file by adding an object as a second argument to your `res.render` call with the variables and their values. Give the `title` a value of `Hello` and `message` a value of `Please log in`. + +It should look like: + +```javascript +res.render('index', { title: 'Hello', message: 'Please log in' }); +``` + +Now refresh your page, and you should see those values rendered in your view in the correct spot as laid out in your `index.pug` file! + +Submit your page when you think you've got it right. If you're running into errors, you can check out the project completed up to this point. # --hints-- Pug should correctly render variables. ```js -(getUserInput) => - $.get(getUserInput('url') + '/').then( - (data) => { - assert.match( - data, - /pug-variable("|')>Please login/gi, - 'Your projects home page should now be rendered by pug with the projects .pug file unaltered' - ); - }, - (xhr) => { - throw new Error(xhr.statusText); - } +async (getUserInput) => { + const url = new URL("/", getUserInput("url")); + const res = await fetch(url); + const data = await res.text(); + assert.match( + data, + /pug-variable("|')>Please log in/gi, + 'Your projects home page should now be rendered by pug with the projects .pug file unaltered' ); +} ``` # --solutions--