+
+
+
+
+
diff --git a/03-task-manager/optionalArrayMethodsReviewExtraAssignment.js b/03-task-manager/optionalArrayMethodsReviewExtraAssignment.js
new file mode 100644
index 0000000000..9b269e25a9
--- /dev/null
+++ b/03-task-manager/optionalArrayMethodsReviewExtraAssignment.js
@@ -0,0 +1,328 @@
+// Review of JavaScript iterative Array methods (`.map`, `.filter` and `.forEach`)
+
+// This is an optional extra assignment
+
+///////////////////////////// Questions ///////////////////////////////////////
+
+// First, some basic background knowledge questions that we discussed during
+// the mentor session
+
+////////////////////
+// What is an array?
+//
+// An array is a data structure which contains a sequence of potentially mixed
+// data types. Structures with multiple elements are sometimes called
+// "collections"
+
+const stuff = [1, 2, 'fish', { id: 3 }];
+
+
+//////////////////////////////
+///// What is a method? /////
+//
+// It is a function that operates on a SPECIFIC data structure. This is a
+// word from Object Oriented Programming (sometimes called OOP).
+//
+// In OOP, we consider functions and data as a hybrid "thing." We call those
+// "things" objects. In JavaScript, EVERYTHING is an object, including
+// functions! But that's kinds of besides the point. The important thing is
+// the conceptual idea: data + behavior is thought of as "one thing."
+
+const object = {
+ // in OO, we group data and behavior
+ data: 1,
+ behavior: function() {
+ // "this" is a reference to the object we are inside of right now
+ this.data++;
+ console.log('OOP demo:', this.data);
+ }
+}
+
+// Every time we call `.behavior()`, the data (number) inside `object` is
+// incremented by 1, so we print "OOP demo 1", "OOP demo 2", etc.
+
+object.behavior()
+object.behavior()
+object.behavior()
+object.behavior()
+object.behavior()
+
+// That's why we call them "array methods" they are methods that exist on the
+// "Array" object.
+
+/////////////////////////////////////////////////
+///// What are the common JS array methods? /////
+//
+// - Array.prototype.push (does not take a callback)
+//
+// These guys all take a callback as input, and then call the callabck for
+// each item in the array
+//
+// - Array.prototype.filter
+// - The callback should return a boolean. If the return value is true, the
+// element becomes a member of the new array. If the return value is false,
+// the element is filtered (removed).
+
+ const integers = [1, 2, 3, 4, 5];
+// evenNumbers will be interger % 2 for each integer
+// '%' is the "modulo" operator. Here we are checking if `integer` divided by 2, leaves a remainder of 0, which is true for even numbers and false for odd numbers.
+ const evenNumbers = integers.filter((integer) => {
+ return integer % 2 === 0
+ })
+
+// - Array.prototype.map
+// - The callback recieves each item of the array. The return value is pushed
+// into a new array
+ const numbers = [1, 2, 3];
+ const doubles = numbers.map((i) => i * 2);
+
+// - Array.prototype.forEach
+// - `forEach` is like a "for" loop. It calls the callback for every item in
+// the array
+ evenNumbers.forEach((thingy) => console.log('even', thingy));
+ doubles.forEach((d) => console.log('doubled!', d));
+
+// - Array.prototype.reduce
+// - A bit tricky
+// - Can transform an array into an atribrary result
+ const lastNames = ['Smith', 'Toure', 'Hernandez']
+ const initialValue = 0;
+ const totalLettersInNames = lastNames.reduce((runningTotal, currentName) => {
+ return runningTotal + currentName.length;
+ }, initialValue)
+ console.log({totalLettersInNames});
+
+ // The first argument is always the return value that we're building up.
+ // I called it, "runningTotal" before. The default name is "accumulator."
+ // Often, Array.prototype.reduce is used to build a mapping from an array,
+ // like this:
+ const people = [{id: 1, name: 'tim'}, {id: 2, name: 'jane'}];
+ const peopleIdMap = people.reduce((map, person) => {
+ map[person.id] = person;
+ return map;
+ }, {} /* second arg is always the initial value! Here, it's an empty object */);
+
+ // Now we can lookup people by id!
+ console.log({lookedUpPerson1: peopleIdMap[1]})
+ console.log({lookedUpPerson2: peopleIdMap[2]});
+
+ // Sometimes, you'll see this fancy syntax used with reduce, especially when
+ // building mappings. Beware, though, there's a lot of unnecessary runtime
+ // overhead here, because we create a new object here every time instead of
+ // re-using the old one!! And it's only a few characters shorter than the
+ // more performant solution above.
+ const peopleNameMap = people.reduce((map, person) => ({
+ ...map,
+ [person.name]: person
+ }), {});
+
+ // Now we can lookup people by name!
+ console.log({lookupTim: peopleNameMap['tim']})
+ console.log({lookupJane: peopleNameMap['jane']});
+
+/////////////////////////// CHALLENGES ////////////////////////////////////////
+
+// Each challenge will be related to this array of names. It will pose a
+// problem related to these names, and then implement the solution. The
+// challenges are:
+//
+// - Create a new array with only each person's last name
+// - Filter names that don't match the format ""
+// - Should remove Tam because she has a double-space
+// - Should remove Carlow because he has a middle-name
+// - Should also remove names like:
+// - "Timothy Cook"
+// - "Nick_Masters"
+// - "Timmy-Turner"
+// - "Billy\nBob"
+// - etc.
+// - Create a new array where everyone's name is converted to "Title Case"
+// - The first character of each word should be uppercase
+// - All other characters in the word should be lowercase
+// - expected output is ['Dimitry Santiago', 'Carlos D. Perez', 'Tam Person', ...]
+// - Last Challenge:
+// Remove names with the wrong format
+// AND change it to "Title Case"
+// AND remove people whose last name ends with z
+// AND write a message asking them to sign up
+//
+// For an extra assignment, you may implement these yourself! Include your
+// changes to this file with your MR for week 3.
+
+const names = [
+ 'Dimitry SantiAgo',
+ 'Carlos d. Perez',
+ 'tam person',
+ 'Mariana Gomez',
+ 'Amy You'
+];
+
+///////////////////////////////////////////////////////////////////////////////
+//// put your answers above if you wish to do the challenges on your own //////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+//////////////////// ////////////////////
+//////////////////// ! ! ////////////////////
+//////////////////// ! Read no further if you wish to ! ////////////////////
+//////////////////// ! do the extra assignment ! ////////////////////
+//////////////////// ! ! ////////////////////
+//////////////////// ////////////////////
+///////////////////////////////////////////////////////////////////////////////
+/////// and maybe also delete or comment-out the remainder of this file! //////
+///////////////////////////////////////////////////////////////////////////////
+///////////////////////////////////////////////////////////////////////////////
+
+//////// CHALLENGE: Get everyone's last name
+const everyonesLastName = names.map((name) => {
+ // `.map` can transform each element 1:1
+ const eachWordSeparated = name.split(" ")
+ // how to get the last index from JS array
+ const lastName = eachWordSeparated.pop();
+ return lastName;
+});
+console.log('everyone last name', everyonesLastName);
+
+//////// CHALLENGE: Filter to the people who followed the right
+// "right format" is "" with a single space!
+const rightFormat = /^\w+ \w+$/;
+const matchesTeachersPedanticFormattingRule = names.filter((name) => {
+ return name.match(rightFormat);
+});
+console.log('good students', matchesTeachersPedanticFormattingRule)
+// (joke :)
+
+
+//////// CHALLENGE: Change everyone's name to "Title Case"
+// (Each Word Uppercase)
+
+// Time complexity is O(n^3): AKA very slow!! This is not an ideal solution in
+// terms of performance, but it does a great job of stretching our understanding
+// of Array.map
+
+// The next section will breakdown this example in much greater detail!
+
+const titledNames = names.map((name) => {
+ // `.map` can transform each element 1:1
+ const eachWordSeparated = name.split(" ")
+
+ const titledName = eachWordSeparated.map((inputWord) => {
+ const inputLetters = inputWord.split("");
+ const wordWithFirstLetterUppercase = inputLetters
+ .map((letter, idx) => (
+ idx === 0
+ ? letter.toUpperCase()
+ : letter.toLowerCase()
+ ))
+ .join("")
+ return wordWithFirstLetterUppercase
+ });
+ return titledName.join(" ")
+});
+console.log('titledNames', titledNames);
+
+// Same example as above (change every name to title case), but I'll break it
+// up into smaller pieces to make it more readable. Each callback function
+// which was "inlined" before are now defined as separate functions, given a
+// name, and documented
+
+/**
+ * This is the callback for the innermost map. Map functions always take two
+ * parameters: the array element, and the index of the array element.
+ *
+ * In this case, we give these 2 pieces meaningful names: characterInWord,
+ * and indexOfCharacter. Remember POSITIONAL arguments (like `(item, index)`)
+ * are identified by POSITION. As long as a POSITIONAL argument is in the
+ * correct POSITION you can give it any name. The best practice is to use
+ * the most descriptive and clear names you can, which we've done here.
+ */
+const transformWordIntoTitle = (characterInWord, indexOfCharacter) => {
+ // We only want to change the FIRST letter of the word to uppercase
+ if (indexOfCharacter === 0) {
+ return characterInWord.toUpperCase();
+ } // we have returned!! The rest of the code will ONLY run for characters
+ // after the first one
+
+ // We could skip `.toLowerCase`, but if a letter in the middle of the word
+ // is uPpErcAse, it'll look nicer if we transform it into lowercase
+ return characterInWord.toLowerCase();
+}
+
+/**
+ * This is the callback used when we are mapping over an array of "words," like:
+ *
+ * ```
+ * ["Carlos", "d.", "Perez"]
+ * ```
+ *
+ * This function receives a string (just ONE of those words, like "d.").
+ *
+ * It will split the word up into an array of letters, use the map function
+ * from before to transform that array into title-case, then join that
+ * transformed array back into a string, and return the result.
+ *
+ * This is the most wasteful & superfluous step. You probably notice we could
+ * just do this instead:
+ *
+ * ```
+ * firstLetter = wordInString[0];
+ * otherLetters = wordInString.splice(1);
+ * return `${firstLetter.toLowerCase()}${otherLetters.toLowerCase()}`
+ * ```
+ *
+ * Indeed, this would be much faster since we avoid an inner loop, but our goal
+ * is to learn, not to go fast!
+ */
+const transformStringIntoTitledWords = (wordInString) => {
+ const letters = wordInString.split('');
+ const titleCaseLetters = letters.map(transformWordIntoTitle);
+ return titleCaseLetters.join('');
+}
+
+/**
+ * Finally, the highest level: this callback operates on every string in our
+ * main name array for this example. It breaks the name up into an array of
+ * words first:
+ *
+ * ```
+ * "carlos cantiago"
+ * -> ["carlos", "cantiago"]
+ * -> ["Carlos", "Santiago"]
+ * -> "Carlos Santiago"
+ * ```
+ */
+const transformNameIntoTitleCase = (name) => {
+ // We'll use a regex to split the string. ' +' means "one or more spaces."
+ // This is good because it'll work for our name "tam person" where there is
+ // a double-space
+ const nameWords = name.split(/ +/);
+ const titleCaseWords = nameWords.map(transformStringIntoTitledWords)
+ return titleCaseWords.join(' ');
+}
+
+console.log(
+ 'titledNames verbose',
+ names.map(transformNameIntoTitleCase)
+)
+
+
+//////// CHALLENGE: Remove names with the wrong format
+// AND change it to "Title Case"
+// AND remove people whose last name ends with z
+// AND write a message asking them to sign up
+const result = names
+ // remove bad format
+ .filter((name) => name.match(rightFormat))
+ // change to title case
+ .map(transformNameIntoTitleCase)
+ // remove names that end in "z"
+ .filter((name) => {
+ const lastLetter = name.slice(-1);
+ return lastLetter.toLowerCase() !== 'z'
+ })
+ // transform into a sign-up message
+ .map((name) => `
+ Hey there ${name}!
+ Want to buy my thing?
+ `);
+
+console.log('result', result);
diff --git a/lessons/ctd-node-assignment-1.md b/lessons/ctd-node-assignment-1.md
new file mode 100644
index 0000000000..f75b13af08
--- /dev/null
+++ b/lessons/ctd-node-assignment-1.md
@@ -0,0 +1,101 @@
+You should already have done the steps described in the **[Getting Started page](./getting-started-with-node-development.md)**. That page describes how to get git, the VSCode Editor, Node, and Postman all installed. All of those should be installed before you start this lesson.
+
+The next step is to create a “fork” of your starter repository for this lesson, which is found **[here](https://github.com/Code-the-Dream-School/node-express-course)**. The fork button is on the upper right of that page. Once the fork is complete, you must `git clone` your fork to get the repository files onto your computer.
+
+Careful!
+
+Please **don’t clone the original Code-the-Dream repository**,
+as if you do that, you will not be able to push your work to Github.
+
+
+
+
+
+You will do all of your work inside the directory created by the `git clone` command. By default, this directory will be called “node-express-course”. Change directories so that you are inside that directory. Then create the **branch** for this week, using the command `git checkout -b week1`.
+
+Now change the directory to the one that says `01-node-tutorial/answers`. You’ll do all of this week’s work inside this directory.
+
+Create the following programs for this lesson, all within the “answers” directory. By the way, there are examples of each of the programs you need to create in the 01-node-tutorial directory (in case you get stuck) but try to do your own work. If you need to review a section of the video for any of these exercises, view the video within Youtube, but not in full screen mode. The panel on the right will show you the chapter of the video so that you know what you should review.
+
+Your homework should include the following programs:
+
+1. `01-intro.js`: This program should use the `console.log` function to write something to the screen. While you are in the “answers” directory, run the command, `node 01-intro.js`, to verify that the program runs. You can also put additional JavaScript logic in your program.
+2. `02-globals.js`: This program should use the `console.log` function to write some globals to the screen. Set an environment variable with the following command in your command line terminal: `export MY_VAR="Hi there!"` The program should then use `console.log` to print out the values of `__dirname` (a Node global variable) and `process.env.MY_VAR` (`process` is also a global, and contains the environment variables you set in your terminal.) You could print out other globals as well ([Node documentation](https://nodejs.org/api/globals.html#global-objects) on available globals). For each of these programs, you invoke them with `node` to make sure they work.
+3. For the next part, you will write multiple programs. `04-names.js`, `05-utils.js`, `06-alternative-flavor.js`, and `07-mind-grenade.js` are modules that you load, using require statements, from the `03-modules.js` file, which is the main program. Remember that you must give the path name in your require statement, for example:
+
+```javascript
+const names = require("./04-names.js");
+```
+
+(3a). `04-names.js` should export multiple values in an object that you will require in `03-module.js`.
+
+(3b). `05-utils.js` should export a single value, which is a function you will call in `03-modules.js`.
+
+(3c). `06-alternative-flavor.js` should export multiple values in the module.exports object, but it should use the alternative approach, adding each value one at a time. The exported values from each should be used in `03-modules.js`, logging results to the console so that you know it is working.
+
+(3d). `07-mind-grenade.js` may not export anything, but it should contain a function that logs something to the console. You should then call that function within the code of `07-mind-grenade.js`. This is to demonstrate that when a module is loaded with a require statement, anything in the mainline code of the loaded module runs.
+**NOTE**: The only program you should need to actually invoke to test that everything is working is `03-modules.js`, because it loads all the others (files 4-7).
+
+1. `08-os-module.js`: This should load the built-in `os` Node module and display some interesting information from the resulting object. As for all modules, you load a reference to it with a require statement, in this case
+
+```javascript
+const os = require("os");
+```
+
+You can look **[here](https://nodejs.org/api/os.html)** for documentation on the stuff in the built-in os module.
+
+1. `09-path-module.js`: This should load the `path` Node module, which is another built-in module. It should then call the `path.join` function to join up a sequence of alphanumeric strings, and it should print out the result. The result will work one way on Windows, where the directory separator is a backslash, and a different way on other platforms, where the directory separator is a slash.
+
+```
+# Example of a Windows path:
+C:\Users\JohnSmith\node-express-course\01-node-tutorial\answers
+
+# Exmaple of a Mac or Linux path:
+/Users/JohnSmith/node-express-course/01-node-tutorial/answers
+```
+
+1. `10-fs-sync.js`: This should load `writeFileSync` and `readFileSync` functions from the `fs` module. Then you will use `writeFileSync` to write 3 lines to a file, `./temporary/fileA.txt`, using the `"append"` flag for each line after the first one. Then use `readFileSync` to read the file, and log the contents to the console. Be sure you create the file in the `temporary` directory. That will ensure that it isn’t pushed to Github when you submit your answers (because that file has been added to the `.gitignore` file for you already which tells git not to look at those files).
+2. `11-fs-async.js`: This should load the `fs` module, and use the asynchronous function `writeFile` to write 3 lines to a file, `./temporary/fileB.txt`. Now, be careful here! This is our first use of **asynchronous functions** in this class, but we are going to use them a lot! First, you need to use the `"append"` flag for all but the first line. Second, each time you write a line to the file, you need to have a callback, because the `writeFile` operation is asynchronous. Third, for each line you write, you need to do the write for the line that follows it within the callback – otherwise the operations won’t happen in order. Put `console.log` statements at various points in your code to tell you when each step completes. Then run the code. Do the console log statements appear in the order you expect? Run the program several times and verify that the file is created correctly. Here is how you might start:
+
+```javascript
+const { writeFile } = require("fs");
+console.log("at start");
+writeFile("./temporary/output.txt", "This is line 1\n", (err, result) => {
+ console.log("at point 1");
+ if (err) {
+ console.log("This error happened: ", err);
+ } else {
+ // here you write your next line
+ }
+});
+console.log("at end");
+```
+
+To get the lines to be written in order, you end up with a long chain of callbacks, which is called “callback hell”. We’ll learn a better way to do this soon.
+
+1. `12-http.js`. This program should use the built-in `http` module to create a simple web server that listens on port 3000\. This is done with the `createServer` function. You pass it a callback function that checks the request variable (`req`) for the current `url` property, and depending on what the URL is, sends back a message to the browser screen. Then have your code listen on port 3000, run this file with the `node` command, and test it from your browser, by navigating to `http://localhost:3000`. You can look at `12-http.js` for the instructor’s answer (except that program listens on 5000). You will need to type in Ctrl+c (the Ctrl key plus the letter “C” at the same time; or for Mac the Cmd key plus the letter “C” at the same time) to exit your program.
+2. Within your “answers” directory is a program called `prompter.js`. This is a program for a simple server. Try it out! It will display a form in the browser when you run the file and navigate to `http://localhost:3000`. Then, when the user submits the form, it echoes back what was submitted, and displays the form again. You don’t have to worry about how it works. There is a simple body parser to read any values submitted with the form, and that parser returns a hash with the name and value of each. Because the parser is asynchronous, you get back the hash in a callback.
+Now, your task is to change this program so that it does something interesting! First, you can change the variables that you want to store when you get the form back. Then, you can change the form itself to return the values you want from the user, which you store in those variables. Then, you can use string interpolation to insert the values of your variables into the HTML. Finally, you change the logic that handles the hash of values you get when the user submits the form, so that you save the values the user submits. The places you would change are marked in the code.
+For example, you could change the input field to be a dropdown with various colors, and you could set the background color of the body to be what the user chooses. Or, you could make a number guessing game: Start with a random number from 1 to 100, let the user guess, and tell the user if their guess is low or high. In this case, you’d change the input field so that it accepts only numeric input (but when it is returned in the hash, it will be a string, so you’d have to convert it.)
+
+When you are done, change directories to the `01-node-tutorial` folder and then do the following to submit your work:
+
+```
+git commit add ./answers
+git commit -m "answers for lesson 1"
+git push -u origin week1
+```
+
+Then go to your GitHub repository – the one you created with a fork (the URL should have your Github username in it). Create a pull request. You may see a yellow banner on your repository if you recently pushed your change, where you can click the “Compare & pull request” to create a PR. Otherwise, you can switch to your branch in the dropdown on the repo page, and then click the pull request icon to create a PR that way.
+
+Careful!
+
+The target of the pull request should be the main branch of the repository
+you created when you forked —
+**not the Code-the-Dream-School main branch!**
+
+Once you have created the pull request, you can copy the URL to link the pull request in your homework submission form. The homework submission form is on the main page for this lesson. This will be the general procedure for submitting homework for this course.
+
+
+
+When you submit your homework, you also submit your answers to the mindset assignment, if there is one for that lesson.
diff --git a/lessons/ctd-node-assignment-10.md b/lessons/ctd-node-assignment-10.md
new file mode 100644
index 0000000000..2134518689
--- /dev/null
+++ b/lessons/ctd-node-assignment-10.md
@@ -0,0 +1,24 @@
+Complete and test all CRUD operations for your model (or the `Jobs` model). Be sure that each operation uses the ID of the user, so that you have good access control.
+
+**Test each step with Postman, creating a Postman collection of tests just like the instructor is doing.**
+
+### Deploying to Render.com
+
+You deploy your application once you have your assignment completed and working, and once you have pushed your `week10` branch to Github.
+
+To deploy to Render.com, follow these steps:
+
+1. Specify the version of Node that Render is to use. One way is to create a `.node-version` file in the root of your project repository, specifying the same version of node as you are running on your machine. On my machine, when I type `node -v`, I get back v16.19.0 . So I would create a `.node-version` file with the single line: `16.19.0`
+2. Create a [Render.com](https://render.com/) account. You do not need to install anything on your workstation for Render deployment. You do not need to enter any credit card information.
+3. From your Render.com dashboard, click on the New button in the upper right. Select Web Service. You will then be prompted to connect a repository. Scroll down to the entry field that says “public git repository”, and enter the URL of your 06-jobs-api repository. Then press continue.
+4. The next page prompts you for the name of the service. This becomes the first part of the URL for your application. You need to give it a unique name that no one else is using, maybe something like `jobs-api-`.
+5. Scroll down to the entry field for branch. Put in `week10`.
+6. Scroll down until you see the “advanced” button. Click on that. You then click on Add Environment Variable. You need to add an environment variable for each of the values in your .env file: the Mongo URI, the JWT key, and so on.
+7. Scroll to the bottom and click on Create Web Service. Render.com then builds the application for deployment. The process takes a while. Once the process completes, you see a link in the upper right for your new web service. The URL will be something like `https://jobs-api-.onrender.com`.
+8. Copy that URL and put it into your Postman tests for the assignment. Then test with Postman. Your application is live!
+
+
+
+### Swagger / OpenAPI documentation
+
+The section of the video from 9:34:30 to the end discusses setting up a Swagger configuration. When you have an API, you need to document it so that implementers of applications that call the API (like the frontend) know what the available endpoints and operations are. Swagger is the best way to do that. It also creates a graphical user interface so that one can call the APIs directly from the UI. You should watch this section so that you understand how a Swagger configuration may be created and what functions it provides. However, this section of the video gets a bit complicated, so you are not required to implement Swagger for your application, but it’s a great idea to watch this section and familiarize yourself with the concept of Swagger. If possible, try to implement it as a bonus task this week.
\ No newline at end of file
diff --git a/lessons/ctd-node-assignment-11.md b/lessons/ctd-node-assignment-11.md
new file mode 100644
index 0000000000..cb16052697
--- /dev/null
+++ b/lessons/ctd-node-assignment-11.md
@@ -0,0 +1,861 @@
+Continue working in the `06-jobs-api` repository that you used for lessons 9 and 10\. Before you start, create a new branch as usual, with a branch name of `week12`. (Week 11 was a catch-up week, so you create the `week12` branch when the `week10` branch is active.)
+
+Create a directory called `public`. This is for the HTML and JavaScript files for the front end. The HTML code you will use is below. Put that in the `public` directory with a file name of `index.html`. (**Reminder:** You will have to change the form, which below is for a job, to match your data model).
+
+This lesson does not involve the creation of any new Node or Express functions. It is all client-side HTML and JavaScript.
+
+```
+
+
+
+
+
+
+ Jobs List
+
+
+
+
Jobs List
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
Company
+
Position
+
Status
+
+
+
+
+
+
+
+
+
+
+
+```
+
+This front end uses a single-page style. There are multiple views in the page, in different DIV elements, but which of these is visible is controlled by the JavaScript you write.
+
+Edit `app.js`. Comment out the following lines:
+
+```javascript
+app.get("/", (req, res) => {
+ res.send('
Jobs API
Documentation');
+});
+```
+
+Add the following line below these commented out lines:
+
+```javascript
+app.use(express.static("public"));
+```
+
+Start the server and go to `localhost:3000` in your browser. You see the page, but it does not do anything. This is because there is no JavaScript to go with it. You create that now. This is the JavaScript for the front end. **Front end JavaScript does not run in Node. Instead, it is delivered to the browser and runs in the browser context, with full access to the `document`, `window`, and `DOM` variables.**
+
+There are various divs to be manipulated by this JavaScript. (Again, you have to change this code to match your data model.) The keep the code organized, we will separate them into various modules (files). There is one module, index.js, to initialize window handling. All the other modules can be imported from there. There are five divs, only one of which will show at a time:
+
+* one to select logon or register,
+* one to do logon,
+* one to do register,
+* one to display the jobs,
+* and one to add or edit a job.
+
+For each of these divs, there is a separate module controlling its operation.
+
+To begin, add the following line to index.html, right above the close of the body tag:
+
+```
+
+```
+
+These modules call one another using the exports that each provides. For this to work, you must declare it as type `module`. Create `index.js` in the public directory. The `index.js` module should read as follows:
+
+```javascript
+let activeDiv = null;
+export const setDiv = (newDiv) => {
+ if (newDiv != activeDiv) {
+ if (activeDiv) {
+ activeDiv.style.display = "none";
+ }
+ newDiv.style.display = "block";
+ activeDiv = newDiv;
+ }
+};
+
+export let inputEnabled = true;
+export const enableInput = (state) => {
+ inputEnabled = state;
+};
+
+export let token = null;
+export const setToken = (value) => {
+ token = value;
+ if (value) {
+ localStorage.setItem("token", value);
+ } else {
+ localStorage.removeItem("token");
+ }
+};
+
+export let message = null;
+
+import { showJobs, handleJobs } from "./jobs.js";
+import { showLoginRegister, handleLoginRegister } from "./loginRegister.js";
+import { handleLogin } from "./login.js";
+import { handleAddEdit } from "./addEdit.js";
+import { handleRegister } from "./register.js";
+
+document.addEventListener("DOMContentLoaded", () => {
+ token = localStorage.getItem("token");
+ message = document.getElementById("message");
+ handleLoginRegister();
+ handleLogin();
+ handleJobs();
+ handleRegister();
+ handleAddEdit();
+ if (token) {
+ showJobs();
+ } else {
+ showLoginRegister();
+ }
+});
+```
+
+Remember that this code is running in the browser, and not in Node. That means you must use `import` and not `require`.
+
+We need to keep track of the active div so that we know which one to disable when switching between them, and that is stored in the variable `activeDiv`. We don’t need to export that variable since it’s only used here in the index script by the `setDiv` function. We export a function that sets the active div, making it visible and hiding the previous active div.
+
+We also have to have a means of enabling or disabling input. This is because we will use asynchronous functions, and the application can get confused if more input comes in while the previous requests are in progress.
+
+We also have to keep track of whether the user is logged in. We do that in a `token` variable that we store in the browser’s local storage (although this creates security risks as previously described.) When local storage is used, the user remains logged in even if the page is refreshed. If the function is called with a `null` token, then we _remove_ the token from local storage instead.
+
+When the user takes actions we may want to display a message on the page. We store the value of that message here in the index script in the `message` variable, so that it can easily be updated by any of the other modules.
+
+Once the DOM is loaded, we load the token (if it exists already) from the browser’s local storage and initialize the handlers for each of the divs.
+
+Then, if the user is logged in, we display the list of jobs. If the user is not logged in, we display the initial panel with a button for logon and a button for register. Note that we need to provide and to export functions to set the enabled flag and the token. This is because one can’t write directly to variables from other modules. Once a variable is `import`ed in a module, it is treated as a `const` variable in that module, so you cannot reassign values to that variable directly.
+
+You will need to create `loginRegister.js`, `register.js`, `login.js`, `jobs.js`, and `addEdit.js` files, all in the public directory.
+
+The `loginRegister.js` module is as follows:
+
+```javascript
+import { inputEnabled, setDiv } from "./index.js";
+import { showLogin } from "./login.js";
+import { showRegister } from "./register.js";
+
+let loginRegisterDiv = null;
+
+export const handleLoginRegister = () => {
+ loginRegisterDiv = document.getElementById("logon-register");
+ const login = document.getElementById("logon");
+ const register = document.getElementById("register");
+
+ loginRegisterDiv.addEventListener("click", (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === login) {
+ showLogin();
+ } else if (e.target === register) {
+ showRegister();
+ }
+ }
+ });
+};
+
+export const showLoginRegister = () => {
+ setDiv(loginRegisterDiv);
+};
+```
+
+Each of the div handling modules follow this pattern. Required imports (used when one div handler calls another) are resolved up front. Then, within the handler function, the div and its controls are defined. Also, within the handler function, an event handler is declared to handle mouse clicks within the div.
+
+A separate function handles display of the div. (React works in similar fashion, if you know that framework — but this lesson does not use React.)
+
+The `register.js` module is as follows:
+
+```javascript
+import {
+ inputEnabled,
+ setDiv,
+ message,
+ token,
+ enableInput,
+ setToken,
+} from "./index.js";
+import { showLoginRegister } from "./loginRegister.js";
+import { showJobs } from "./jobs.js";
+
+let registerDiv = null;
+let name = null;
+let email1 = null;
+let password1 = null;
+let password2 = null;
+
+export const handleRegister = () => {
+ registerDiv = document.getElementById("register-div");
+ name = document.getElementById("name");
+ email1 = document.getElementById("email1");
+ password1 = document.getElementById("password1");
+ password2 = document.getElementById("password2");
+ const registerButton = document.getElementById("register-button");
+ const registerCancel = document.getElementById("register-cancel");
+
+ registerDiv.addEventListener("click", (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === registerButton) {
+ showJobs();
+ } else if (e.target === registerCancel) {
+ showLoginRegister();
+ }
+ }
+ });
+};
+
+export const showRegister = () => {
+ email1.value = null;
+ password1.value = null;
+ password2.value = null;
+ setDiv(registerDiv);
+};
+```
+
+The `login.js` module is as follows:
+
+```javascript
+import {
+ inputEnabled,
+ setDiv,
+ token,
+ message,
+ enableInput,
+ setToken,
+} from "./index.js";
+import { showLoginRegister } from "./loginRegister.js";
+import { showJobs } from "./jobs.js";
+
+let loginDiv = null;
+let email = null;
+let password = null;
+
+export const handleLogin = () => {
+ loginDiv = document.getElementById("logon-div");
+ email = document.getElementById("email");
+ password = document.getElementById("password");
+ const logonButton = document.getElementById("logon-button");
+ const logonCancel = document.getElementById("logon-cancel");
+
+ loginDiv.addEventListener("click", (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === logonButton) {
+ showJobs();
+ } else if (e.target === logonCancel) {
+ showLoginRegister();
+ }
+ }
+ });
+};
+
+export const showLogin = () => {
+ email.value = null;
+ password.value = null;
+ setDiv(loginDiv);
+};
+```
+
+The `jobs.js` module is as follows:
+
+```javascript
+import {
+ inputEnabled,
+ setDiv,
+ message,
+ setToken,
+ token,
+ enableInput,
+} from "./index.js";
+import { showLoginRegister } from "./loginRegister.js";
+import { showAddEdit } from "./addEdit.js";
+
+let jobsDiv = null;
+let jobsTable = null;
+let jobsTableHeader = null;
+
+export const handleJobs = () => {
+ jobsDiv = document.getElementById("jobs");
+ const logoff = document.getElementById("logoff");
+ const addJob = document.getElementById("add-job");
+ jobsTable = document.getElementById("jobs-table");
+ jobsTableHeader = document.getElementById("jobs-table-header");
+
+ jobsDiv.addEventListener("click", (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === addJob) {
+ showAddEdit(null);
+ } else if (e.target === logoff) {
+ showLoginRegister();
+ }
+ }
+ });
+};
+
+export const showJobs = async () => {
+ setDiv(jobsDiv);
+};
+```
+
+The `addEdit.js` module is as follows:
+
+```javascript
+import { enableInput, inputEnabled, message, setDiv, token } from "./index.js";
+import { showJobs } from "./jobs.js";
+
+let addEditDiv = null;
+let company = null;
+let position = null;
+let status = null;
+let addingJob = null;
+
+export const handleAddEdit = () => {
+ addEditDiv = document.getElementById("edit-job");
+ company = document.getElementById("company");
+ position = document.getElementById("position");
+ status = document.getElementById("status");
+ addingJob = document.getElementById("adding-job");
+ const editCancel = document.getElementById("edit-cancel");
+
+ addEditDiv.addEventListener("click", (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === addingJob) {
+ showJobs();
+ } else if (e.target === editCancel) {
+ showJobs();
+ }
+ }
+ });
+};
+
+export const showAddEdit = (job) => {
+ message.textContent = "";
+ setDiv(addEditDiv);
+};
+```
+
+Create all these files and then try the application out. You will find that the application now allows for user interaction, and you can navigate between the active divs.
+
+However, the application still does not do much, because, of course, there is no code there to communicate with the back end. This is to be added using the `fetch()` function. Fetch makes REST calls, and it returns results asynchronously. In the code that follows, we use the `async/await` pattern, so of course whenever that is used, the surrounding function must be declared as an `async` function.
+
+Also, we need to disable input during the period in which the `async` operation is in progress. We do this by setting the `inputEnabled` flag using the `enableInput` function that is exported from `index.js`. Our click handlers ignore clicks if they occur while the `inputEnabled` flag is `false`.
+
+We may get an error when making the request to the server, so the async operations must be surrounded with a `try/catch`. If an error occurs, we notify the user by displaying a message in the page, but we also log the error to the console. You may not want to log all errors to the console in a production application, but it is wise to do it when you are developing, so that you can find your own errors.
+
+First, we’ll make register and logon work. For either register or logon, if the step is successful, the back end returns a JWT token. This is stored for use in accessing jobs records.
+
+Adding these capabilities to `register.js` gives the following:
+
+```javascript
+import {
+ inputEnabled,
+ setDiv,
+ message,
+ token,
+ enableInput,
+ setToken,
+} from "./index.js";
+import { showLoginRegister } from "./loginRegister.js";
+import { showJobs } from "./jobs.js";
+
+let registerDiv = null;
+let name = null;
+let email1 = null;
+let password1 = null;
+let password2 = null;
+
+export const handleRegister = () => {
+ registerDiv = document.getElementById("register-div");
+ name = document.getElementById("name");
+ email1 = document.getElementById("email1");
+ password1 = document.getElementById("password1");
+ password2 = document.getElementById("password2");
+ const registerButton = document.getElementById("register-button");
+ const registerCancel = document.getElementById("register-cancel");
+
+ registerDiv.addEventListener("click", async (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === registerButton) {
+ if (password1.value != password2.value) {
+ message.textContent = "The passwords entered do not match.";
+ } else {
+ enableInput(false);
+
+ try {
+ const response = await fetch("/api/v1/auth/register", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ name: name.value,
+ email: email1.value,
+ password: password1.value,
+ }),
+ });
+
+ const data = await response.json();
+ if (response.status === 201) {
+ message.textContent = `Registration successful. Welcome ${data.user.name}`;
+ setToken(data.token);
+
+ name.value = "";
+ email1.value = "";
+ password1.value = "";
+ password2.value = "";
+
+ showJobs();
+ } else {
+ message.textContent = data.msg;
+ }
+ } catch (err) {
+ console.error(err);
+ message.textContent = "A communications error occurred.";
+ }
+
+ enableInput(true);
+ }
+ } else if (e.target === registerCancel) {
+ name.value = "";
+ email1.value = "";
+ password1.value = "";
+ password2.value = "";
+ showLoginRegister();
+ }
+ }
+ });
+};
+
+export const showRegister = () => {
+ email1.value = null;
+ password1.value = null;
+ password2.value = null;
+ setDiv(registerDiv);
+};
+```
+
+Notice that we’ve now made the click event listener callback function `async`. We then check to see if the user entered matching passwords in the two inputs. If they did, disable clicking on the buttons and then make the `fetch()` call to the register endpoint.
+
+If the call is successful, we parse the data from the response, display a message to the user, and save the token to local storage using the function exported from `index.js`. We can then display the jobs page. If the call was not successful we will continue to show the register page and will display the error message from the response to the user. If any errors occur, then we catch them and continue to show the register page and show a generic error message to the user.
+
+After we’re done processing the call (whether it was successful or not) we re-enable clicking on the buttons.
+
+Notice that we always clear out the input values before we switch to another page. We do not want those to live on in memory.
+
+The `login.js` module becomes:
+
+```javascript
+import {
+ inputEnabled,
+ setDiv,
+ token,
+ message,
+ enableInput,
+ setToken,
+} from "./index.js";
+import { showLoginRegister } from "./loginRegister.js";
+import { showJobs } from "./jobs.js";
+
+let loginDiv = null;
+let email = null;
+let password = null;
+
+export const handleLogin = () => {
+ loginDiv = document.getElementById("logon-div");
+ email = document.getElementById("email");
+ password = document.getElementById("password");
+ const logonButton = document.getElementById("logon-button");
+ const logonCancel = document.getElementById("logon-cancel");
+
+ loginDiv.addEventListener("click", async (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === logonButton) {
+ enableInput(false);
+
+ try {
+ const response = await fetch("/api/v1/auth/login", {
+ method: "POST",
+ headers: {
+ "Content-Type": "application/json",
+ },
+ body: JSON.stringify({
+ email: email.value,
+ password: password.value,
+ }),
+ });
+
+ const data = await response.json();
+ if (response.status === 200) {
+ message.textContent = `Logon successful. Welcome ${data.user.name}`;
+ setToken(data.token);
+
+ email.value = "";
+ password.value = "";
+
+ showJobs();
+ } else {
+ message.textContent = data.msg;
+ }
+ } catch (err) {
+ console.error(err);
+ message.textContent = "A communications error occurred.";
+ }
+
+ enableInput(true);
+ } else if (e.target === logonCancel) {
+ email.value = "";
+ password.value = "";
+ showLoginRegister();
+ }
+ }
+ });
+};
+
+export const showLogin = () => {
+ email.value = null;
+ password.value = null;
+ setDiv(loginDiv);
+};
+```
+
+Make these changes and test the application again. You should find that you can register and logon. Logoff doesn’t work right at present, but this can be corrected in `jobs.js` with the following change:
+
+```javascript
+ } else if (e.target === logoff) {
+ setToken(null);
+
+ message.textContent = "You have been logged off.";
+
+ jobsTable.replaceChildren([jobsTableHeader]);
+
+ showLoginRegister();
+ }
+```
+
+Note that logoff involves no communication with the back end. The user is logged off by deleting the JWT from memory. We also have to clear the jobs data from memory, for security reasons, so that a non-logged-in user can’t see the previously logged-in user’s jobs. That’s what the replaceChildren does here: it replaces the contents of the `
` element with just the `
` element and nothing else.
+
+Next we need to make the changes so that we can create job entries. The `addEdit.js` module is changed as follows:
+
+```javascript
+addEditDiv.addEventListener("click", async (e) => {
+ if (inputEnabled && e.target.nodeName === "BUTTON") {
+ if (e.target === addingJob) {
+ enableInput(false);
+
+ let method = "POST";
+ let url = "/api/v1/jobs";
+ try {
+ const response = await fetch(url, {
+ method: method,
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ company: company.value,
+ position: position.value,
+ status: status.value,
+ }),
+ });
+
+ const data = await response.json();
+ if (response.status === 201) {
+ // 201 indicates a successful create
+ message.textContent = "The job entry was created.";
+
+ company.value = "";
+ position.value = "";
+ status.value = "pending";
+
+ showJobs();
+ } else {
+ message.textContent = data.msg;
+ }
+ } catch (err) {
+ console.log(err);
+ message.textContent = "A communication error occurred.";
+ }
+
+ enableInput(true);
+ } else if (e.target === editCancel) {
+ message.textContent = "";
+ showJobs();
+ }
+ }
+});
+```
+
+In this case, we have to pass the JWT in the header for the call to work.
+
+Once you have added this code, try out the application again. You are now able to add entries, but you can’t actually see them. Next you add the code to populate the table of jobs entries. Of course, this involves another fetch operation, passing the JWT as for the add. Then the results are used to populate a table.
+
+There is a somewhat tricky part to this. We want to have edit and delete buttons for each row of the table. But, how do we associate an edit button with the edit operation, and when it is clicked, how do we know which entry is to be edited? This is done as follows. The edit buttons are given a class of `"editButton"`, and similarly, the delete buttons are given a class of `"deleteButton"`. We can also add an `data-` attribute to the buttons. These are called [data attributes](https://developer.mozilla.org/en-US/docs/Web/API/HTMLElement/dataset) and in Javascript they correspond to a DOM hash/object called `dataset`. By adding a `data-id` attribute to the elemnet, we can access it via the `dataset.id` property. We want to set that id to be the id of that job (or your custom object), as returned from the database.
+
+It looks like this in `jobs.js`:
+
+```javascript
+export const showJobs = async () => {
+ try {
+ enableInput(false);
+
+ const response = await fetch("/api/v1/jobs", {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ const data = await response.json();
+ let children = [jobsTableHeader];
+
+ if (response.status === 200) {
+ if (data.count === 0) {
+ jobsTable.replaceChildren(...children); // clear this for safety
+ } else {
+ for (let i = 0; i < data.jobs.length; i++) {
+ let rowEntry = document.createElement("tr");
+
+ let editButton = `
`;
+ let deleteButton = `
`;
+ let rowHTML = `
+
${data.jobs[i].company}
+
${data.jobs[i].position}
+
${data.jobs[i].status}
+
${editButton}${deleteButton}
`;
+
+ rowEntry.innerHTML = rowHTML;
+ children.push(rowEntry);
+ }
+ jobsTable.replaceChildren(...children);
+ }
+ } else {
+ message.textContent = data.msg;
+ }
+ } catch (err) {
+ console.log(err);
+ message.textContent = "A communication error occurred.";
+ }
+ enableInput(true);
+ setDiv(jobsDiv);
+};
+```
+
+So, plug this code into `jobs.js` at the appropriate point, and then try the application again. You should now be able to see the entries for each job.
+
+However, the edit and delete buttons don’t actually work. This is because the click handler in `jobs.js` isn’t set to look for them yet. We can add a section to the click handler to remedy this.
+
+```javascript
+ } else if (e.target.classList.contains("editButton")) {
+ message.textContent = "";
+ showAddEdit(e.target.dataset.id);
+ }
+```
+
+The `dataset.id` contains the id of the entry to be edited. That is then passed on to the showAddEdit function. So we need to change that function to do something with this parameter.
+
+This function is in `addEdit.js`, and should be changed as follows:
+
+```javascript
+export const showAddEdit = async (jobId) => {
+ if (!jobId) {
+ company.value = "";
+ position.value = "";
+ status.value = "pending";
+ addingJob.textContent = "add";
+ message.textContent = "";
+
+ setDiv(addEditDiv);
+ } else {
+ enableInput(false);
+
+ try {
+ const response = await fetch(`/api/v1/jobs/${jobId}`, {
+ method: "GET",
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ });
+
+ const data = await response.json();
+ if (response.status === 200) {
+ company.value = data.job.company;
+ position.value = data.job.position;
+ status.value = data.job.status;
+ addingJob.textContent = "update";
+ message.textContent = "";
+ addEditDiv.dataset.id = jobId;
+
+ setDiv(addEditDiv);
+ } else {
+ // might happen if the list has been updated since last display
+ message.textContent = "The jobs entry was not found";
+ showJobs();
+ }
+ } catch (err) {
+ console.log(err);
+ message.textContent = "A communications error has occurred.";
+ showJobs();
+ }
+
+ enableInput(true);
+ }
+};
+```
+
+With this change, the `add/edit` div will be displayed with the appropriate values. If an add is being done, the function is called with a null parameter, and the form comes up blank with an add button. If an edit is being done, the function is called with the id of the entry to edit. The job is then retrieved from the database and the input fields are populated, and the button is changed to say update. We also store the id of the entry in the `dataset.id` of the `addEdit` div, so we keep track of which entry is to be updated.
+
+So far, so good, but what happens when the user clicks on the update button? In this case, we need to do a PATCH instead of a POST, and we need to include the id of the entry to be updated in the URL. So we need the following additional changes to addEdit.js:
+
+```javascript
+if (e.target === addingJob) {
+ enableInput(false);
+
+ let method = "POST";
+ let url = "/api/v1/jobs";
+
+ if (addingJob.textContent === "update") {
+ method = "PATCH";
+ url = `/api/v1/jobs/${addEditDiv.dataset.id}`;
+ }
+
+ try {
+ const response = await fetch(url, {
+ method: method,
+ headers: {
+ "Content-Type": "application/json",
+ Authorization: `Bearer ${token}`,
+ },
+ body: JSON.stringify({
+ company: company.value,
+ position: position.value,
+ status: status.value,
+ }),
+ });
+
+ const data = await response.json();
+ if (response.status === 200 || response.status === 201) {
+ if (response.status === 200) {
+ // a 200 is expected for a successful update
+ message.textContent = "The job entry was updated.";
+ } else {
+ // a 201 is expected for a successful create
+ message.textContent = "The job entry was created.";
+ }
+
+ company.value = "";
+ position.value = "";
+ status.value = "pending";
+ showJobs();
+ } else {
+ message.textContent = data.msg;
+ }
+ } catch (err) {
+ console.log(err);
+ message.textContent = "A communication error occurred.";
+ }
+ enableInput(true);
+}
+```
+
+Make these changes, and editing jobs should work. Make sure that adding a new job still work correctly.
+
+This completes all CRUD operations except for delete. You will notice that the delete buttons don’t work yet. Fix this on your own by following the pattern used for the edit button.
+
+You’ll call the jobs delete API, and in the URL you will include the ID of the entry to be deleted. Then display the job list page if the delete was successful.
+
+**Note:** There is an error in the implementation of the delete operation in the jobs controller. The instructor’s guidance is to use this line:
+
+```javascript
+res.status(StatusCodes.OK).send();
+```
+
+This is incorrect, because an empty body is not valid JSON. Change it to:
+
+```javascript
+res.status(StatusCodes.OK).json({ msg: "The entry was deleted." });
+```
+
+If you do not make this change, an exception is thrown on the front end when you attempt to parse the response body with response.json().
+
+You should be making commits as you go along. Once you have everything working, do a last `git add` and `git commit`, then push your `week12` branch to your Github repository. Then modify the Render.com deployment you have to point to the new branch. This will cause your new code to be deployed to Render.com. Verify that your application front end is working on Render.com.
+
+### Tips on Getting the Delete to Work
+
+* How do you know that the user wants to make a delete request? Each of the delete buttons is given a class of `deleteButton`. You check for that class in the e.target.
+* How do you know which entry to delete? The id of the entry is stored in the `dataset.id` of the button.
+* How do you do the delete? You need a call to `fetch` with a method of `DELETE` giving the URL of that entry. Be sure you include the JWT in the authorization header. Also, remember that fetch is asynchronous, and should be called in a `try/catch` block.
+* What do you do if the delete succeeds? First, you put a message in the text content of the message paragraph. Second, you redraw the table showing the updated list of entries. The jobs.js module has a function for this.
+* What do you do if the delete fails? Put a message indicating the failure in the message paragraph.
+* Anything else? You don’t want to take input while these asynchronous operations are in progress, so you set the enabled flag before you start them, and clear it afterwards.
\ No newline at end of file
diff --git a/lessons/ctd-node-assignment-12.md b/lessons/ctd-node-assignment-12.md
new file mode 100644
index 0000000000..39961e5c1e
--- /dev/null
+++ b/lessons/ctd-node-assignment-12.md
@@ -0,0 +1,372 @@
+In this project (which continues for the next 3 lessons), you create a jobs application an alternate way from the previous session. This time, using server-side rendering with the `EJS` templating language. If you haven’t started your final project, you can use this project as the basis, using server-side rendering instead of the front-end + back-end model. Of course, to satisfy the requirements of the rubric, you would need to modify the schema to manage objects that are different from the jobs model.
+
+### Creating the Repository
+
+There is no starter repository for this project, so you will need to create one, with the following steps. First, create a `jobs-ejs` folder/directory on your computer (not in the tree of a previous project), and `cd` (change directories) into it. **Make sure that it is not in the tree of a previous project, that is, running `git status` _should_ return an error after you create the directory**. Next, create the `.gitignore` file and the `.env` file. The `.gitignore` file is critical to make sure your Mongo credentials and the Node libraries are not stored in Github. It should have these lines:
+
+```
+/node_modules
+.env
+.DS_Store
+```
+
+You can just copy the `.gitignore` file and the `.env` file from the `06-jobs-api` directory. You won’t use the JWT values from `.env`, so you can delete those. The `.env` file is needed for the Mongo credential, and eventually for the session secret.
+
+Next, run `git init` to make the directory a git repository. Then, log into Github and create a new repository called `jobs-ejs`. You create a new repository using the + button in the upper right of your Github screen. You do not use a template, the repository should be public, and you do not create a `README` or a `.gitignore`.
+
+
+
+Once the repository has been created, you need to associate the Github repository with the repository on your laptop. You will see the following screen:
+
+
+
+Click on the clipboard icon next to the URL to copy it. Then, in your laptop session for the jobs-ejs repository, type the following, where the URL is the one you copied to your clipboard
+
+```
+git remote add origin
+git add -A
+git commit -m "first commit"
+git push origin main
+```
+
+The local repository is now associated with your Github repository. There isn’t much in it yet, just the `.gitignore`. Now create the `lesson12` branch, where you will do your work.
+
+### Components and Directories
+
+You need to initialize NPM for your repository. So, do an `npm init`. You can take all the defaults when prompted. This creates the `package.json`, and enables the installation of npm packages. You need to add `scripts` for dev and start to the `package.json` so that you can do `npm run` or `npm run dev`, where the `dev` script runs app.js using `nodemon` and the plain `npm run` runs it using `node`.
+
+You need to install the following packages:
+
+```
+bcryptjs
+connect-flash
+connect-mongodb-session
+cookie-parser
+dotenv
+ejs
+express
+express-async-errors
+express-rate-limit
+express-session
+helmet
+host-csrf
+mongoose
+passport
+passport-local
+xss-clean
+```
+
+You should also install `eslint`, `prettier`, and `nodemon` as **dev dependencies** if you haven’t installed them globally. You’ve seen some of these packages before, but not others. Each will be explained as we use them.
+
+You will write to the Mongo database, so to save time, you can copy two directory trees from the `06-jobs-api` directory. You can use the following commands (you may have to adjust these depending on your directory structure / where you’ve set up your folders):
+
+```
+cp -r ../06-jobs-api/db .
+cp -r ../06-jobs-api/models .
+```
+
+Now create the directory structure you will use, in particular the following directories:
+
+```
+controllers
+routes
+middleware
+utils
+views
+views/partials
+```
+
+You do not need a `public` directory. The pages are rendered by the `EJS` engine from the `views` directory.
+
+Next, create the boilerplate `app.js`. It should look as follows:
+
+```javascript
+const express = require("express");
+require("express-async-errors");
+
+const app = express();
+
+app.set("view engine", "ejs");
+app.use(require("body-parser").urlencoded({ extended: true }));
+
+// secret word handling
+let secretWord = "syzygy";
+app.get("/secretWord", (req, res) => {
+ res.render("secretWord", { secretWord });
+});
+app.post("/secretWord", (req, res) => {
+ secretWord = req.body.secretWord;
+ res.redirect("/secretWord");
+});
+
+app.use((req, res) => {
+ res.status(404).send(`That page (${req.url}) was not found.`);
+});
+
+app.use((err, req, res, next) => {
+ res.status(500).send(err.message);
+ console.log(err);
+});
+
+const port = process.env.PORT || 3000;
+
+const start = async () => {
+ try {
+ app.listen(port, () =>
+ console.log(`Server is listening on port ${port}...`)
+ );
+ } catch (error) {
+ console.log(error);
+ }
+};
+
+start();
+```
+
+This boilerplate has crude page not found handling, as well as error handling. Those functions will be moved to middleware eventually. It also has something mysterious to handle the `/secretWord` route. So, if you run this app as is, it will return page not found for all URLs, except for `/secretWord`. That one returns an error — because we haven’t created the secretWord view! Once we have created the view, the `res.render` operation will display it. Note that we also need an `app.use` statement for the body parser.
+
+## First EJS file
+
+Create `views/secretWord.ejs`. The file should look as follows:
+
+```
+
+
+
+
+
+
+
+ Jobs List
+
+
+
The secret word is: <%= secretWord %>
+
Would you like to change it?
+
+
+
+```
+
+This is an EJS file, but it looks just like HTML — except that section in `<%= %>`. Enclosed in those tags is JavaScript that is executed on the server side to modify the template. In this case, it just inserts the value of the `secretWord` variable. This value is passed to the EJS file via the second argument of the `res.render` function call in `app.js`.
+
+**Keep in mind** that the value must be a string or something that JavaScript knows how to convert to a string with a `toString()` method. If you try to pass in an object or some other complex data structure, you’ll likely just see it rendered as `[object Object]`. One way to handle that case would be to use the `JSON.stringify(object)` function to convert an object into a string.
+
+Note also that the `POST` operation does a `redirect`, telling the browser which URL should be displayed after processing is complete. This ends up calling `GET /secretWord` again, this time displaying the updated `secretWord` value.
+
+Try opening the URL. You should see that the secret word is displayed, and it can be changed.
+
+If the application has a lot of boilerplate, headers and footers and so on, we don’t want to have to duplicate that for every page. So we use “partials”. Create the following files:
+
+`views/partials/head.ejs`
+
+```
+
+
+
+
+
+
+ Jobs List
+
+
+```
+
+`views/partials/header.ejs`
+
+```
+
The Jobs EJS Application
+
+```
+
+`views/partials/footer.ejs`
+
+```
+
+
A copyright could go here.
+
+
+```
+
+Then change `views/secretWord.ejs` to substitute include statements, so that the whole thing reads:
+
+```
+<%- include("partials/head.ejs") %>
+<%- include("partials/header.ejs") %>
+
The secret word is: <%= secretWord %>
+
Would you like to change it?
+
+<%- include("partials/footer.ejs") %>
+```
+
+You use the `<%- %>` to include HTML from other files. Be sure that you only use it with HTML that you trust, otherwise you could introduce a security exposure. Try the new page out.
+
+The data we are displaying is just the _value_ of the `secretWord` variable — but we could also insert data into the page that was retrieved from the database, as we’ll see.
+
+### Sessions
+
+There are a couple problems with the handling of the secret word. First, the value is stored globally — so every user sees the same value. We want the user to see only their own data. The second problem is that the data is stored in the memory of the server process, so when that server is restarted, the value is lost. We fix this using sessions. (Sessions may also be used with front-end+back-end applications.)
+
+Sessions are associated with a cookie, as we’ll see, and they are protected with a secret. So add a line to `.env` with this secret, as follows:
+
+```
+SESSION_SECRET=123lkawjg091u82378429
+```
+
+The secret is some hard to guess string — and you **_never_** want to publicize it to Github! Then, add the following lines to `app.js`. These lines should be added _before_ any of the lines that govern routes, such as the `app.get` and `app.post` statements:
+
+```javascript
+require("dotenv").config(); // to load the .env file into the process.env object
+const session = require("express-session");
+app.use(
+ session({
+ secret: process.env.SESSION_SECRET,
+ resave: false,
+ saveUninitialized: true,
+ })
+);
+```
+
+Change the logic so that the secret word is stored and retrieved in the session, as follows:
+
+```javascript
+// let secretWord = "syzygy"; <-- comment this out or remove this line
+app.get("/secretWord", (req, res) => {
+ if (!req.session.secretWord) {
+ req.session.secretWord = "syzygy";
+ }
+ res.render("secretWord", { secretWord: req.session.secretWord });
+});
+app.post("/secretWord", (req, res) => {
+ req.session.secretWord = req.body.secretWord;
+ res.redirect("/secretWord");
+});
+```
+
+`req.session` is an object that will persist across requests.
+
+Then try the URL. You will see that it works as before, except that if you have different sessions (from different browsers; or from a regular tab and an incognito tab) the value of the secretWord is different. However, if you restart the server, the value is lost. This is because while we’re no longer storing `secretWord` as a global variable, it is still being stored in the memory of the server.
+
+If you go into developer tools in your browser, and click on the `Application` tab, you can check that a cookie with the name of `connect.sid` has been associated with your browser session.
+
+
+
+This is the key used to retrieve session data. You can also see that the `HttpOnly` flag is set, so that browser-side code can’t access this cookie.
+
+We want to store the session data in a durable way. To do this, we’ll use Mongo as a session store. Replace the one line that does the `app.use` for session with all of these lines:
+
+```javascript
+const MongoDBStore = require("connect-mongodb-session")(session);
+const url = process.env.MONGO_URI;
+
+const store = new MongoDBStore({
+ // may throw an error, which won't be caught
+ uri: url,
+ collection: "mySessions",
+});
+store.on("error", function (error) {
+ console.log(error);
+});
+
+const sessionParms = {
+ secret: process.env.SESSION_SECRET,
+ resave: true,
+ saveUninitialized: true,
+ store: store,
+ cookie: { secure: false, sameSite: "strict" },
+};
+
+if (app.get("env") === "production") {
+ app.set("trust proxy", 1); // trust first proxy
+ sessionParms.cookie.secure = true; // serve secure cookies
+}
+
+app.use(session(sessionParms));
+```
+
+These lines cause the session to be stored in Mongo. The bit about `sessionParms.cookie.secure = true` is important but may not be immediately obvious. It is saying that, if the application is running in production, the session cookie won’t work unless SSL is present. It’s a good policy, but as you are not running in production, you don’t have SSL.
+
+Then try the app again. Now you see that the `secretWord` value is preserved even if the server is restarted. If you go to your Mongo database, you can see the session data there — although it is not human-readable.
+
+### Flash Messages
+
+As the user performs operations, you need to inform them of the result. You do this with the `connect-flash` package. This stores the result of operations so that they can be subsequently displayed to the user. This information can’t be kept in server storage, because otherwise each user could see the others’ messages. And it can’t be stored in the req or res objects, because after an operation, there is typically a redirect, and the information would be lost. Therefore, the `connect-flash` package relies on the _session_. The package also keeps track of whether the message has already been displayed, so that it is only shown once. You can store multiple messages at different severity.
+
+Add the following code. Note that this code must come after the `app.use` that sets up sessions, because flash depends on sessions:
+
+```
+app.use(require("connect-flash")());
+```
+
+We want to set some messages into flash. To do this, change the `POST` route for `/secretWord` to look like this:
+
+```javascript
+app.post("/secretWord", (req, res) => {
+ if (req.body.secretWord.toUpperCase()[0] == "P") {
+ req.flash("error", "That word won't work!");
+ req.flash("error", "You can't use words that start with p.");
+ } else {
+ req.session.secretWord = req.body.secretWord;
+ req.flash("info", "The secret word was changed.");
+ }
+ res.redirect("/secretWord");
+});
+```
+
+These messages should be displayed on the next screen. Note that you can have multiple info or error messages. In order for them to be displayed, we need to add code in the view, in the `header.js` partial template, as follows:
+
+```
+
The Jobs EJS Application
+<% if (errors) {
+ errors.forEach((err) => { %>
+
+<% }) } %>
+
+```
+
+Whoa! you may be saying. That doesn’t look like HTML! What will the browser do with it? The answer is that the browser never sees this stuff. The things in `<% %>` are JavaScript, executed on the server side, and the render process removes this and replaces it with the result of the code. There is logic, which is executed but not displayed, in the `<% %>` parts. Then, there is substitution of values, in the `<%= %>` parts, where the equals sign indicates that a value is to be displayed. So, this code checks the `info` and `errors` arrays, displaying values from them if any are present.
+
+But, the problem is that the `info` and `errors` arrays need to get passed into the EJS file, when the render is called. This could be done as follows:
+
+```javascript
+res.render("secretWord", {
+ secretWord,
+ errors: flash("error"),
+ info: flash("info"),
+});
+```
+
+But this is a little clumsy, because if we have a bunch of pages we render, every render statement would have to be modified. So, instead, we put the values in `res.locals`. That hash contains values that are always available to the EJS rendering engine. As follows:
+
+```javascript
+app.get("/secretWord", (req, res) => {
+ if (!req.session.secretWord) {
+ req.session.secretWord = "syzygy";
+ }
+ res.locals.info = req.flash("info");
+ res.locals.errors = req.flash("error");
+ res.render("secretWord", { secretWord: req.session.secretWord });
+});
+```
+
+This is how `res.locals` is loaded with the right stuff. However, we’d want to move the `res.locals` statements into a middleware routine that always runs (_after_ the flash middleware, but _before_ any of the routes), and we’ll do that eventually.
+
+### Submitting Your Work
+
+To submit your work, you add, commit, and push your branch as usual, create a pull request and include a link to your pull request in the homework submission. In the next lesson, we will implement authentication using Passport, and in the final lesson, we’ll manage Jobs entries in the database.
\ No newline at end of file
diff --git a/lessons/ctd-node-assignment-13.md b/lessons/ctd-node-assignment-13.md
new file mode 100644
index 0000000000..ac60cab4a6
--- /dev/null
+++ b/lessons/ctd-node-assignment-13.md
@@ -0,0 +1,467 @@
+In this lesson, you use the `passport` and `passport-local` npm packages to handle user authentication, from within a server-side rendered application.
+
+### First Steps
+
+You continue to work with the same repository as the previous lesson, but you create a new branch called `lesson13`.
+
+The user records are stored in the Mongo database, just as for the Jobs API lesson. You have already copied the `models` directory tree from the Jobs API lesson into the `jobs-ejs` repository. The user model is used unchanged. You configure passport to use that model.
+
+To begin, you will need the following views:
+
+```
+views/index.ejs
+views/logon.ejs
+views/register.ejs
+```
+
+The `index.ejs` view just shows links to login or register. The `logon.ejs` view collects the email and password from the user. The register collects the name, email, and password for a new user. We want to use the partials as well. We want to modify the header partial to give the name of the logged on user, and to add a logoff button if a user is logged on.
+
+`views/index.ejs`:
+
+```
+<%- include("partials/head.ejs") %>
+<%- include("partials/header.ejs") %>
+<% if (user) { %>
+ Click this link to view/change the secret word.
+<% } else { %>
+ Click this link to logon.
+ Click this link to register.
+<% } %>
+<%- include("partials/footer.ejs") %>
+```
+
+`views/logon.ejs`:
+
+```
+<%- include("partials/head.ejs") %>
+<%- include("partials/header.ejs") %>
+
+<%- include("partials/footer.ejs") %>
+```
+
+`views/register.js`
+
+```
+<%- include("partials/head.ejs") %>
+<%- include("partials/header.ejs") %>
+
+<%- include("partials/footer.ejs") %>
+```
+
+Revised `views/partials/header.ejs`:
+
+```
+
+ <% })
+ } %>
+
+```
+
+These changes won’t suffice to do anything in the application, until routes are added to match. We need to follow best practices, with separate route and controller files.
+
+### Router and Controller
+
+Create a file `routes/sessionRoutes.js`, as follows:
+
+```javascript
+const express = require("express");
+// const passport = require("passport");
+const router = express.Router();
+
+const {
+ logonShow,
+ registerShow,
+ registerDo,
+ logoff,
+} = require("../controllers/sessionController");
+
+router.route("/register").get(registerShow).post(registerDo);
+router
+ .route("/logon")
+ .get(logonShow)
+ .post(
+ // passport.authenticate("local", {
+ // successRedirect: "/",
+ // failureRedirect: "/sessions/logon",
+ // failureFlash: true,
+ // })
+ (req, res) => {
+ res.send("Not yet implemented.");
+ }
+ );
+router.route("/logoff").post(logoff);
+
+module.exports = router;
+```
+
+Ignore the passport lines for the moment. This just sets up the routes. We need to create a corresponding file `controllers/sessionController.js`. Here we use the `User` model. However, the file you copied makes some references to the JWT library. You must edit `models/User.js` to remove those references in order for `User.js` to load. We aren’t using JWTs in this project.
+
+```javascript
+const User = require("../models/User");
+const parseVErr = require("../util/parseValidationErr");
+
+const registerShow = (req, res) => {
+ res.render("register");
+};
+
+const registerDo = async (req, res, next) => {
+ if (req.body.password != req.body.password1) {
+ req.flash("error", "The passwords entered do not match.");
+ return res.render("register", { errors: flash("errors") });
+ }
+ try {
+ await User.create(req.body);
+ } catch (e) {
+ if (e.constructor.name === "ValidationError") {
+ parseVErr(e, req);
+ } else if (e.name === "MongoServerError" && e.code === 11000) {
+ req.flash("error", "That email address is already registered.");
+ } else {
+ return next(e);
+ }
+ return res.render("register", { errors: flash("errors") });
+ }
+ res.redirect("/");
+};
+
+const logoff = (req, res) => {
+ req.session.destroy(function (err) {
+ if (err) {
+ console.log(err);
+ }
+ res.redirect("/");
+ });
+};
+
+const logonShow = (req, res) => {
+ if (req.user) {
+ return res.redirect("/");
+ }
+ res.render("logon", {
+ errors: req.flash("error"),
+ info: req.flash("info"),
+ });
+};
+
+module.exports = {
+ registerShow,
+ registerDo,
+ logoff,
+ logonShow,
+};
+```
+
+We have two endpoints that handle just rendering an EJS page, `registerShow` and `logonShow`.
+
+We then have endpoints for actually performing the logoff and register actions. We don’t need a controller handler for login, because Passport handles that for us.
+
+The `registerDo` handler will check if the two passwords the user entered match, and refresh the page otherwise. If all is good there, it will create a user in the database and redirect to the home page. The creation of the user entry in Mongo is just the same as it was for the Jobs API. We also have some error handling cases here.
+
+If there is a Mongoose validation error when creating a user record, we need to parse the validation error object to return the issues to the user in a more helpful format, and we do that in the file util/parseValidationErrs.js:
+
+```javascript
+const parseValidationErrors = (e, req) => {
+ const keys = Object.keys(e.errors);
+ keys.forEach((key) => {
+ req.flash("error", key + ": " + e.errors[key].properties.message);
+ });
+};
+
+module.exports = parseValidationErrors;
+```
+
+We need some middleware to load `res.locals` with any variables we need, like the logged in user and flash properties. Create `middleware/storeLocals.js`:
+
+```javascript
+const storeLocals = (req, res, next) => {
+ if (req.user) {
+ res.locals.user = req.user;
+ } else {
+ res.locals.user = null;
+ }
+ res.locals.info = req.flash("info");
+ res.locals.errors = req.flash("error");
+ next();
+};
+
+module.exports = storeLocals;
+```
+
+Now, we need a couple of `app.use` statements. Add these lines right after the `connect-flash` line:
+
+```javascript
+app.use(require("./middleware/storeLocals"));
+app.get("/", (req, res) => {
+ res.render("index");
+});
+app.use("/sessions", require("./routes/sessionRoutes"));
+```
+
+The storeLocals middleware sets the values for errors, info, and user, but in registerDo above, we have to pass the value for errors on the render call, because we changed the value of the flash errors after storeLocals ran.
+
+We are now using the database. So, we need to connect to it at startup. You need a file, `db/connect.js`. Check that it looks like the following:
+
+```javascript
+const mongoose = require("mongoose");
+
+const connectDB = (url) => {
+ return mongoose.connect(url, {});
+};
+
+module.exports = connectDB;
+```
+
+Then add this line to `app.js`, just before the listen line:
+
+```javascript
+await require("./db/connect")(process.env.MONGO_URI);
+```
+
+Then try the application out, starting at the `"/"` URL. You can try each of the new views. But you still can’t logon. The logon operation is commented out, because Passport is not set up.
+
+### Configuring Passport
+
+To use Passport, you have to tell it how to authenticate users, retrieving them from the database. Create a file `passport/passportInit.js`, as follows:
+
+```javascript
+const passport = require("passport");
+const LocalStrategy = require("passport-local").Strategy;
+const User = require("../models/User");
+
+const passportInit = () => {
+ passport.use(
+ "local",
+ new LocalStrategy(
+ { usernameField: "email", passwordField: "password" },
+ async (email, password, done) => {
+ try {
+ const user = await User.findOne({ email: email });
+ if (!user) {
+ return done(null, false, { message: "Incorrect credentials." });
+ }
+
+ const result = await user.comparePassword(password);
+ if (result) {
+ return done(null, user);
+ } else {
+ return done(null, false, { message: "Incorrect credentials." });
+ }
+ } catch (e) {
+ return done(e);
+ }
+ }
+ )
+ );
+
+ passport.serializeUser(async function (user, done) {
+ done(null, user.id);
+ });
+
+ passport.deserializeUser(async function (id, done) {
+ try {
+ const user = await User.findById(id);
+ if (!user) {
+ return done(new Error("user not found"));
+ }
+ return done(null, user);
+ } catch (e) {
+ done(e);
+ }
+ });
+};
+
+module.exports = passportInit;
+```
+
+Here we are registering a “strategy” to tell Passport how to handle requests that we pass to it `passport.use` is a function that takes the name you want to give this passport strategy (here we’re using `"local"`) and the strategy as the second argument, where we’re passing in a `new LocalStrategy` from the `passport-local` library.
+
+The `LocalStrategy` constructor takes two arguments: an options object, and a callback function. The options object expects us to tell the strategy what field on the request will have the user identifier, and what field will have the password. Here, we tell it to look for `"email"` as the identifier, and `password` for the password.
+
+The second argument passed to the `LocalStrategy` constructor is the callback function that will be called when we do `passport.authenticate(...)` in the login route in `sessionRoutes.js`. The `local-strategy` library will extract the `email` and `password` fields that we defined in the options/first argument, from the `req.body`. If it doesn’t find those fields, it will return a 400 error. If it does find them, then it will then pass those (along with a `done` callback) to the function we’ve defined above. It’s up to us then to write the code specific to our application, that handles checking if the user exists and has used to correct password.
+
+The `done` callback accepts three arguments:
+
+1. An error, if there is one, otherwise just pass in `null`
+2. The user object, if the user is found and used valid credentials, otherwise `false`
+3. An object with a `message` property and `type`, this can be used in both error and success cases to show flash messages. Here we’e only using it in non-error failure cases (user not found or wrong credentials used).
+
+In addition to registering our local strategy, we also define `serializeUser` and `deserializeUser` functions on the `passport` object.
+
+`serializeUser` is used when saving a user to the request session object. We can’t save an object to the session cookie, so we need to “serialize” it a.k.a. encode it as a string. In the above function, we’re doing that by telling Passport to save just the user id to the session cookie.
+
+Then when Passport is handling a protected route, it will use the `deserializeUser` function to retrieve the user from the database, using the id that was saved to the session cookie.
+
+You can now add the following lines to `app.js`, right _after_ the `app.use` for session (Passport relies on session):
+
+```javascript
+const passport = require("passport");
+const passportInit = require("./passport/passportInit");
+
+passportInit();
+app.use(passport.initialize());
+app.use(passport.session());
+```
+
+First we call the `passportInit` function that we created in the previous section. This registers our `local` Passport strategy, and sets up the `serializeUser` and `deserializeUser` functions onto the `passport` object.
+
+Then we call `passport.initialize()` (which sets up Passport to work with Express and sessions) and `passport.session()` (which sets up an Express middleware that runs on all requests, checks the session cookie for a user id, and if it finds one, deserializes and attaches it to the `req.user` property).
+
+Finally, you can now uncomment the lines having to do with Passport in `routes/sessionRoutes.js`, so that the require statement for Passport is included, and so that the route for logon looks like
+
+```javascript
+router
+ .route("/logon")
+ .get(logonShow)
+ .post(
+ passport.authenticate("local", {
+ successRedirect: "/",
+ failureRedirect: "/sessions/logon",
+ failureFlash: true,
+ })
+ );
+```
+
+This means that when someone sends a `POST` request to the `/sessions/logon` path, Passport will call the function we defined earlier and registered to the name `"local"`. If that function completes successfully (`done(...)` is called with no error argument), then it will redirect the page to the `successRedirect` page. If there is an error, then it will redirect to the `failureRedirect` page, and also set a flash message with the `message` property from the object we passed to `done(...)`.
+
+Since we’re letting Passport handle setting the `req.flash` properties now, we can remove the lines in `controllers/sessionController.js` that set the flash messages for the `loginShow` handler. So that should now just look like:
+
+```javascript
+const logonShow = (req, res) => {
+ if (req.user) {
+ return res.redirect("/");
+ }
+ res.render("logon");
+};
+```
+
+After that, try logon for one of the accounts you have created. You should see that you are logged in and can access the `secretWord` page. You should also see appropriate error messages for bad logon credentials. Also, test logoff.
+
+### Protecting a Route
+
+To protect a route, you need some middleware, as follows.
+
+`middleware/auth.js`:
+
+```javascript
+const authMiddleware = (req, res, next) => {
+ if (!req.user) {
+ req.flash("error", "You can't access that page before logon.");
+ res.redirect("/");
+ } else {
+ next();
+ }
+};
+
+module.exports = authMiddleware;
+```
+
+`req.user` is injected into the `req` object by `passport.session()`. We can check if it exists (a.k.a. if the user is logged-in) in this middleware and use that to determine if the non-logged-in requester should be redirected to the home page, or if the logged-in user should be allowed to continue to the next middleware or controller handler function.
+
+We want to protect any route for the `"/secretWord"` path. The best practice is to put the code for those routes into a router file, as follows.
+
+`routes/secretWord.js`:
+
+```javascript
+const express = require("express");
+const router = express.Router();
+
+router.get("/", (req, res) => {
+ if (!req.session.secretWord) {
+ req.session.secretWord = "syzygy";
+ }
+
+ res.render("secretWord", { secretWord: req.session.secretWord });
+});
+
+router.post("/", (req, res) => {
+ if (req.body.secretWord.toUpperCase()[0] == "P") {
+ req.flash("error", "That word won't work!");
+ req.flash("error", "You can't use words that start with p.");
+ } else {
+ req.session.secretWord = req.body.secretWord;
+ req.flash("info", "The secret word was changed.");
+ }
+
+ res.redirect("/secretWord");
+});
+
+module.exports = router;
+```
+
+We could further refactor this by moving the code for handling the routes (`(req, res) => {...}`) a controller file, like we’ve done for the session routes, but we’ll leave it like this for now.
+
+Next let’s replace the `app.get` and `app.post` statements for the `"/secretWord"` routes in `app.js` with these lines:
+
+```javascript
+const secretWordRouter = require("./routes/secretWord");
+app.use("/secretWord", secretWordRouter);
+```
+
+Then try out the secretWord page to make sure it still works. Turning on protection is simple. You add the authentication middleware to the route above as follows:
+
+```javascript
+const auth = require("./middleware/auth");
+app.use("/secretWord", auth, secretWordRouter);
+```
+
+That causes the authentication middleware to run before the `secretWordRouter`, and it redirects if any requests are made for those routes before logon. Try it out: login and verify that you can see and change the secretWord. Then log off and try to go to the `"/secretWord"` URL.
+
+### Submitting Your Work
+
+As usual, add and commit your changes and push the `lesson13` branch to your Github. Then create the pull request and include the link in your homework submission.
\ No newline at end of file
diff --git a/lessons/ctd-node-assignment-14.md b/lessons/ctd-node-assignment-14.md
new file mode 100644
index 0000000000..8e21276f0a
--- /dev/null
+++ b/lessons/ctd-node-assignment-14.md
@@ -0,0 +1,125 @@
+You continue to work in the `jobs-ejs` repository. Create a branch called `lesson14` for this week’s work.
+
+### Fixing the Security
+
+Passport is using the session cookie to determine if the user is logged in. This creates a security vulnerability called “cross site request forgery” (CSRF). We will demonstrate this.
+
+To see this, clone **[this repository](https://github.com/Code-the-Dream-School/csrf-attack)** into a separate directory, outside of the current `jobs-ejs` folder. Then, within the directory you cloned, install packages with `npm install` and run the app with `node app`. This will start another express application listening on port **4000** of your local machine. This is the attacking code. It could be running anywhere on the Internet — that has nothing to do with the attack.
+
+You should have two browser tabs open, one for localhost:3000, and one for localhost:4000\. The one at localhost:4000 just shows a button that says Click Me! **Don’t click it yet**. Use the `jobs-ejs` application in the 3000 tab to set the secret string to some value. Then close the tab for localhost:3000\. Then open a new tab for localhost:3000\. Then check the value of the secret string. So far so good — it still has the value you set. If you log off, your session is discarded. Try this: Log off. Then click the button in the localhost:4000 tab. Then log back on and view the secret string. It is back to syzygy. Set it to a custom value.
+
+Now, without logging off of jobs-ejs , click the button in the 4000 tab. Then refresh the /secretWord page in `jobs-ejs`. Hey, what happened! (By the way, this attack would succeed even if you closed the 3000 tab entirely.)
+
+You see, the other application sends a request to your application in the context of your browser — and that browser request automatically includes the cookie. So, the application thinks the request comes from a logged on user, and honors it. If the application, as a result of a form post, makes database changes, or even transfers money, the attacker could do that as well.
+
+So, how to fix this? This is the purpose of the host-csrf package you installed at the start of the project. Follow the instructions **[here](https://www.npmjs.com/package/host-csrf#:~:text=The%20csrf%20middleware,Example%3A)** to integrate the package with your application. You will need to change app.js as well as **each of the forms** in your EJS files. You can use `process.env.SESSION_SECRET` as your `cookie-parser` secret. Note that the `app.use` for the CSRF middleware must come _after_ the cookie parser middleware and _after_ the body parser middleware, but _before_ any of the routes. You will see a message logged to the console that the CSRF protection is not secure. That is because you are using HTTP, not HTTPS, so the package is less secure in this case, but you would be using HTTPS in production. As you will see, it stops the attack.
+
+Re-test, first to see that your application still works, and second, to see that the attack no longer works. (A moral: Always log off of sensitive applications before you surf, in case the sensitive application is vulnerable in this way. Also note that it does not help to close the application, as the cookie is still present in the browser. You have to log off to clear the cookie. Even restarting the browser does not suffice.)
+
+Enabling CSRF protection in the project is an _important_ part of this lesson — don’t omit it! By the way, the CSRF attack only works when the credential is in a cookie. It doesn’t work if you use JWTs in the authorization header. However, as we've seen, to send JWTs in an authorization header, you have to store sensitive data in browser local storage, which is always a bad idea.
+
+### A Couple of Tips
+
+The rest of this lesson shows how to build a dynamic database application with **no client-side JavaScript.** Of course, in real-world applications, you’ll often have client side JavaScript, but this lesson shows that you can do a lot of things without it.
+
+However, it does necessitate some differences in approach. If all you have on the client side is HTML, the client can only send `GET` requests (for links) or `POST` requests (for submitting a form). You can’t send `PUT`, `PATCH`, or `DELETE` operations from HTML — unless you add in some client-side JavaScript. So, in this lesson, all routes are GET and POST routes.
+
+You are going to get a list of job listings and display them in a table. You are also going to enable the user to create a new job listing, edit an existing one, or delete one from the list. As always, a given user can only access the entries they own, and not other people’s. Because you can’t do `PUT`, `PATCH`, or `DELETE`, you’ll do `POST` operations for each of these, giving a different URL for each so that the server knows what to do. **Never add, update, or delete data using a `GET`.** That would introduce security vulnerabilities.
+
+Your table should have columns for each the attributes (company, position, status) of each job listing. In addition, it should have buttons on each row to edit or delete an entry. Editing an entry starts with a `GET` to display a form. Deleting an entry just sends a `POST`. So, you should have routes something like this:
+
+```
+GET /jobs (display all the job listings belonging to this user)
+POST /jobs (Add a new job listing)
+GET /jobs/new (Put up the form to create a new entry)
+GET /jobs/edit/:id (Get a particular entry and show it in the edit box)
+POST /jobs/update/:id (Update a particular entry)
+POST /jobs/delete/:id (Delete an entry)
+```
+
+In your table, you’ll have a button for edit and a button for delete. The button for edit should do a GET, so that’s a link. A good way to make a link look like a button is to put the button inside the link, as follows:
+
+```
+
+
+ With a little creativity, it is amazing how much we can build with
+ these simple primitives. For example:
+
+
+
+
+
+ ${
+ // I am using strings that say "true" and "false" which is a bit
+ // confusing. These are strings, not boolean values! Hence, I actually
+ // have a lot more than 2 possibilities; I only turn the lights on
+ // if I have an exact "true". I turn them off for an exact "false".
+ // Otherwise, I do nothing!
+ request.query["lightsOn"] === "true"
+ ? ""
+ : request.query["lightsOn"] === "false"
+ ? ""
+ : ""
+ }
+
+
+