diff --git a/.gitignore b/.gitignore index f4cecba1d..b6b402ed9 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,10 @@ .DS_Store bin -homework-solution +assignments-solution node_modules npm-debug.log package-lock.json -yarn-error.log \ No newline at end of file +yarn-error.log +*.bkp + +week3/prep-exercise/server-demo/ diff --git a/.vscode/launch.json b/.vscode/launch.json index 8071fa0cb..6a1000df1 100644 --- a/.vscode/launch.json +++ b/.vscode/launch.json @@ -7,24 +7,22 @@ { "type": "node", "request": "launch", - "name": "Launch Week 1 - Homework", - "program": "${workspaceFolder}/week1/homework/src/index.js", - "cwd": "${workspaceFolder}/week1/homework" + "name": "Launch Week 1 - Assignments", + "program": "${workspaceFolder}/week1/assignments/src/index.js", + "cwd": "${workspaceFolder}/week1/assignments" }, { "type": "node", "request": "launch", - "name": "Launch Week 1 - Homework Tests", - "program": "${workspaceRoot}/week1/homework/node_modules/ava/profile.js", + "name": "Launch Week 1 - Assignments Tests", + "program": "${workspaceRoot}/week1/assignments/node_modules/ava/profile.js", "args": [ "--concurrency=1", "--no-color", "--serial", - "${workspaceRoot}/week1/homework/test/server.test.js" + "${workspaceRoot}/week1/assignments/test/server.test.js" ], - "skipFiles": [ - "/**/*.js" - ] + "skipFiles": ["/**/*.js"] }, { "type": "node", @@ -46,6 +44,20 @@ "name": "Launch Week 3 - Lecture", "program": "${workspaceFolder}/week3/lecture/src/index.js", "cwd": "${workspaceFolder}/week3/lecture" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Week 3 - Prep exercise", + "program": "${workspaceFolder}/week3/prep-exercise/server/app.js", + "cwd": "${workspaceFolder}/week3//prep-exercise" + }, + { + "type": "node", + "request": "launch", + "name": "Launch Week 3 - Prep exercise demo", + "program": "${workspaceFolder}/week3/prep-exercise/server-demo/app.js", + "cwd": "${workspaceFolder}/week3//prep-exercise" } ] } diff --git a/.vscode/settings.json b/.vscode/settings.json index e1c3e7296..4fec15ae8 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -1,8 +1,7 @@ { "editor.tabSize": 2, - "eslint.autoFixOnSave": true, "javascript.validate.enable": false, "editor.codeActionsOnSave": { - "source.fixAll.eslint": true - }, + "source.fixAll.eslint": "explicit" + } } diff --git a/README.md b/README.md index c1b745e46..c0b709052 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ > If you are following the HackYourFuture curriculum we recommend you to start with module 1: [HTML/CSS/GIT](https://github.com/HackYourFuture/HTML-CSS). To get a complete overview of the HackYourFuture curriculum first, click [here](https://github.com/HackYourFuture/curriculum). -> Please help us improve and share your feedback! If you find better tutorials or links, please share them by [opening a pull request](https://github.com/HackYourFuture/JavaScript1/pulls). +> Please help us improve and share your feedback! If you find better tutorials or links, please share them by [opening a pull request](https://github.com/HackYourFuture/Node.js/pulls). # Module #5 - Understand backend: creating web servers with JavaScript using Node.js (Backend) @@ -16,20 +16,20 @@ During the following 2 weeks you'll be learning all about this. As a tool to ill ## Learning goals -In this module you will get familiar with the world of backend development. By the end of it you have learned: +In this module you get familiar with the world of backend development. By the end of it, you have learned: -- What is meant by the term `backend` -- The `client-server` model -- What `HTTP` and `REST` mean -- How to `create your own web servers` with Node.js, using `Express.js` -- What a `templating engine` is. -- How to use the `Node Package Manager (NPM)`. -- How to use Express.js to make a `RESTful API` -- How to build a small `full-stack application` +- What is meant by the term `backend`; +- The `client-server` model; +- What `HTTP` and `REST` mean; +- How to `create your own web servers` with Node.js, using `Express.js`; +- What a `templating engine` is; +- How to use the `Node Package Manager (NPM)`; +- How to use Express.js to make a `RESTful API`; +- How to build a small `full-stack application`. ## Before you start -Before you start you need to install a very important software: Node.js! We're going to use the latest stable version of it, which is **v16.x**. Click on the following link to download it to your computer: +Before you start you need to install a very important software: Node.js! We're using the latest stable version of it, which is **v16.x**. Click on the following link to download it to your computer: - For [Ubuntu](https://github.com/nodesource/distributions#debinstall) - For [macOS](https://nodejs.org/en/download/) @@ -43,22 +43,22 @@ Verify the installation by running `node -v` (-v is short for version) from the This repository consists of 3 essential parts: -1. `README`: this document contains all the required theory you need to understand **while** working on the homework. It contains not only the right resources to learn about the concepts, but also lectures done by HackYourFuture teachers. This is the **first thing** you should start with every week -2. `MAKEME`: this document contains the instructions for each week's homework. Start with the exercises rather quickly, so that you can ground the concepts you read about earlier. +1. `README`: this document contains all the required theory you need to understand **while** working on the assignments. It contains not only the right resources to learn about the concepts, but also lectures done by HackYourFuture teachers. This is the **first thing** you should start with every week +2. `MAKEME`: this document contains the instructions for each week's assignments. Start with the exercises rather quickly, so that you can ground the concepts you read about earlier. 3. `LESSONPLAN`: this document is meant for teachers as a reference. However, as a student don't be shy to take a look at it as well! ### How to study Let's say you are just starting out with the Node.js module. This is what you do... -1. The week always starts on **Wednesday**. First thing you'll do is open the `README.md` for that week. For the first week of `Node.js`, that would be [Week1 Reading](/Week1/README.md) -2. You spend **Wednesday** and **Thursday** going over the resources and try to get a basic understanding of the concepts. In the meanwhile, you'll also implement any feedback you got on last week's homework (from the JavaScript3 module) -3. On **Friday** you start with the homework, found in the `MAKEME.md`. For the first week of `Node.js`, that would be [Week1 Homework](/Week1/MAKEME.md) +1. The week always starts on **Wednesday**. First thing you'll do is open the `README.md` for that week. For the first week of `Node.js`, that would be [Week1 Reading](./week1/README.md) +2. You spend **Wednesday** and **Thursday** going over the resources and try to get a basic understanding of the concepts. In the meanwhile, you'll also implement any feedback you got on last week's assignments (from the Using API's module) +3. On **Friday** you start with the assignments, found in the `MAKEME.md`. For the first week of `Node.js`, that would be [Week1 Assignments](/week1/MAKEME.md) 4. You spend **Friday** and **Saturday** playing around with the exercises and write down any questions you might have 5. **DEADLINE 1**: You'll submit any questions you might have before **Saturday 23.59**, in the class channel 6. On **Sunday** you'll attend class. It'll be of the Q&A format, meaning that there will be no new material. Instead your questions shall be discussed and you can learn from others -7. You spend **Monday** and **Tuesday** finalizing your homework -8. **DEADLINE 2**: You submit your homework to the right channels (GitHub) before **Tuesday 23.59**. If you can't make it on time, please communicate it with your mentor +7. You spend **Monday** and **Tuesday** finalizing your assignments +8. **DEADLINE 2**: You submit your assignments to the right channels (GitHub) before **Tuesday 23.59**. If you can't make it on time, please communicate it with your mentor 9. Start the new week by going back to point 1! In summary: @@ -69,7 +69,7 @@ To have a more detailed overview of the guidelines, please read [this document]( ### Video lectures -For each module HackYourFuture provides you with video lectures. These are made by experienced software developers who know what they're talking about. The main teacher for this module will be [Andrej Gajduk](https://hackyourfuture.slack.com/team/UL0P2MB52): Product Owner and Senior Full-Stack Developer! +For each module, HackYourFuture provides you with video lectures. These are made by experienced software developers who know what they're talking about. The main mentor for this module is [Andrej Gajduk](https://hackyourfuture.slack.com/team/UL0P2MB52): Product Owner and Senior Full-Stack Developer! You can find out more about him here: @@ -83,10 +83,11 @@ Learn from Andrej in the following playlist of videos he has made for you! (Clic ## Planning -| Week | Topic | Readings | Homework | Lesson Plan | -| ---: | ----------------------------------- | ------------------------------ | ------------------------------ | ------------------------------------- | -| 1. | Client-server model, HTTP & Express | [Readings W1](week1/README.md) | [Homework W1](week1/MAKEME.md) | [Lesson Plan W1](week1/LESSONPLAN.md) | -| 2. | REST, CRUD, API calls, Testing | [Readings W2](week2/README.md) | [Homework W2](week2/MAKEME.md) | [Lesson Plan W2](week2/LESSONPLAN.md) | +| Week | Topic | Readings | Assignments | Lesson Plan | +| ---: | ----------------------------------- | ------------------------------ | --------------------------------- | ------------------------------------- | +| 1. | Client-server model, HTTP & Express | [Readings W1](week1/README.md) | [Assignments W1](week1/MAKEME.md) | [Lesson Plan W1](week1/LESSONPLAN.md) | +| 2. | REST, CRUD, API calls | [Readings W2](week2/README.md) | [Assignments W2](week2/MAKEME.md) | [Lesson Plan W2](week2/LESSONPLAN.md) | +| 3. | User Authentication, session management, Testing | [Readings W3](week3/README.md) | [Assignments W3](week3/MAKEME.md) | [Lesson Plan W3](week3/LESSONPLAN.md) | ## Finished? diff --git a/assets/nodejs.png b/assets/nodejs.png index 33f1445e0..c89e275cb 100644 Binary files a/assets/nodejs.png and b/assets/nodejs.png differ diff --git a/homework/config-files/babel.config.cjs b/assignments/config-files/babel.config.cjs similarity index 100% rename from homework/config-files/babel.config.cjs rename to assignments/config-files/babel.config.cjs diff --git a/homework/config-files/jest.config.js b/assignments/config-files/jest.config.js similarity index 100% rename from homework/config-files/jest.config.js rename to assignments/config-files/jest.config.js diff --git a/hand-in-homework-guide.md b/hand-in-assignments-guide.md similarity index 52% rename from hand-in-homework-guide.md rename to hand-in-assignments-guide.md index f9dce4f07..bb46f92ae 100644 --- a/hand-in-homework-guide.md +++ b/hand-in-assignments-guide.md @@ -1,18 +1,18 @@ -# How to hand in homework +# How to hand in assignments -In this module you'll submit your homework only using GIT and GitHub. +In this module you'll submit your assignments only using GIT and GitHub. 1. [GitHub](https://www.github.com/HackYourFuture/Node.js) -## 1. GitHub homework guide +## 1. GitHub assignments guide -HYF Video +HYF Video -Watch the video (by clicking the image) or go through the following walk-through to learn how to submit your homework: +Watch the video (by clicking the image) or go through the following walk-through to learn how to submit your assignments: ONE TIME ONLY (START OF EVERY MODULE) -1. Create a [fork](https://help.github.com/en/articles/fork-a-repo) of the homework module repository. For Node.js, the homework module repository is `https://www.github.com/HackYourHomework/Node.js-classXX` where XX is your class number. You do this by using the `fork` option on the top right +1. Create a [fork](https://help.github.com/en/articles/fork-a-repo) of the assignments module repository. For Node.js, the assignments module repository is `https://www.github.com/HackYourAssignment/Node.js-cohortXX` where XX is your class number. You do this by using the `fork` option on the top right 2. Navigate to the URL of the cloned repository (it should be in your personal GitHub account, under "repositories") 3. Clone the repository, using SSH, to your local machine. You can do this by typing in `git clone ` in the command line 4. On your local machine, navigate to the folder using the command line @@ -21,19 +21,18 @@ ONE TIME ONLY (START OF EVERY MODULE) EVERY WEEK 1. Do a `git pull` on your main branch to get the latest version. -2. Create a new branch for each week you have homework. For example, for the week 1 homework for Node create a branch called `YOUR_NAME-w1-Node`. Don't forget to checkout this branch after creating it. -3. Make your homework! -4. Once you're finished, add your homework to a commit. Make sure you *only* commit your homework files and nothing else. You can use `git add -p` if you only want to add a couple files. You can always check what is happening with the `git status` command (as one of our mentors always says, it is the console.log of git!). -5. Create the commit (`git commit`). Make the commit message meaningful, for example `finished project for homework week1`. +2. Create a new branch for each week you have assignments. For example, for the week 1 assignments for Node create a branch called `YOUR_NAME-w1-Node`. Don't forget to checkout this branch after creating it. +3. Make your assignments! +4. Once you're finished, add your assignments to a commit. Make sure you _only_ commit your assignments files and nothing else. You can use `git add -p` if you only want to add a couple files. You can always check what is happening with the `git status` command (as one of our mentors always says, it is the console.log of git!). +5. Create the commit (`git commit`). Make the commit message meaningful, for example `finished project for assignments week1`. 6. Push the branch to your forked repository 7. On the GitHub page of your forked repository, click on the `create pull request` button. Make sure the `base repository` is your teacher's repository, on branch master 8. Give the pull request a title in the following format: ```markdown -Homework week 1 +Assignments week 1 ``` 9. Submit the pull request from your forked repository branch into the `main` branch If you have any questions or if something is not entirely clear ¯\\\_(ツ)\_/¯, please ask/comment on Slack! - diff --git a/week1/LESSONPLAN.md b/week1/LESSONPLAN.md index c2af96a22..71d80fa8f 100644 --- a/week1/LESSONPLAN.md +++ b/week1/LESSONPLAN.md @@ -33,7 +33,7 @@ Node.js is a server-side platform, that allows us to use JavaScript to write bac Show students how to use Node.js to execute JavaScript files. Start with the following simple example, but explain how this log will be found inside of the command line instead of ```js -console.log('Hello World!'); +console.log("Hello World!"); ``` #### Exercise @@ -42,14 +42,14 @@ In a new JavaScript file write a function that returns true if a day is a weeken ```javascript function isWeekend(dayOfWeek) { - if (dayOfWeek === 'Saturday') return true; - if (dayOfWeek === 'Monday') return false; + if (dayOfWeek === "Saturday") return true; + if (dayOfWeek === "Monday") return false; // fill in the rest } -console.log('Tuesday is a ' + (isWeekend('Tuesday') ? 'weekend' : 'week day')); // week day -console.log('Friday is a ' + (isWeekend('Friday') ? 'weekend' : 'week day')); // week day -console.log('Sunday is a ' + (isWeekend('Sunday') ? 'weekend' : 'week day')); // weekend +console.log("Tuesday is a " + (isWeekend("Tuesday") ? "weekend" : "week day")); // week day +console.log("Friday is a " + (isWeekend("Friday") ? "weekend" : "week day")); // week day +console.log("Sunday is a " + (isWeekend("Sunday") ? "weekend" : "week day")); // weekend ``` Execute the file with `node` in the command line. @@ -153,11 +153,11 @@ In a new folder: Create a JavaScript file called `server.js`, with the following code: ```javascript -const express = require('express'); +const express = require("express"); const app = express(); const PORT = 3000; -app.get('/', (req, res) => res.send('Hello World!')); +app.get("/", (req, res) => res.send("Hello World!")); app.listen(PORT, () => console.log(`Example app listening on port ${PORT}!`)); ``` @@ -176,7 +176,7 @@ Write an Express app that serves the following HTML:

Things to do

    -
  • Write homework
  • +
  • Write assignments
  • Buy groceries
  • Prepare dinner
  • Watch movie
  • @@ -185,7 +185,6 @@ Write an Express app that serves the following HTML: ``` - #### Essence Express.js is used to easily create web servers, that allow us (among other reasons) to serve HTML so our browser can read it. The browser sends a request to a specific address, like `/`, and our server (through Express) responds with an HTML file. @@ -193,24 +192,31 @@ Express.js is used to easily create web servers, that allow us (among other reas ### 5. Serving static files with Express #### Explanation + Motiviation based on previous exercise where HTML code is put in the javascript. Instead of doing this, the HTML can be saved in a file in the project and then send with express when needed. #### Example + Save the HTML content in a new file in the project e.g. `index.html`. Then modify the javascript code to serve the HTML from the file instead of having it hardcoded. + ```javascript -const express = require('express'); +const express = require("express"); const app = express(); const PORT = 3000; -app.get('/', (req, res) => res.sendfile('index.html')); +app.get("/", (req, res) => res.sendfile("index.html")); app.listen(PORT, () => console.log(`Example app listening on port ${PORT}!`)); ``` #### Exercise + ```css -li:nth-child(even) {background-color: #CCC} +li:nth-child(even) { + background-color: #ccc; +} ``` + If time left: Save the above css in a file `style.css`. Link the stylesheet in the HTML file. Extend the express app to return the sylesheet for route `\style.css`. diff --git a/week1/MAKEME.md b/week1/MAKEME.md index e1d35d0ff..e2bd1d275 100644 --- a/week1/MAKEME.md +++ b/week1/MAKEME.md @@ -1,4 +1,4 @@ -# Homework Node.js Week 1 +# Assignments Node.js Week 1 ## Todo List @@ -46,9 +46,9 @@ Inside of your `Node.js` fork, go to the folder `week1`. Inside of that folder, ## **5. PROJECT: HackYourTemperature I** -> In this part of the homework you'll be setting up the basis of your project: `HackYourTemperature`. Inside the folder `homework`, create a new folder called `hackyourtemperature`. You'll add to it every week. +> In this part of the assignments you'll be setting up the basis of your project: `HackYourTemperature`. Inside the folder `assignments`, create a new folder called `hackyourtemperature`. You'll add to it every week. -In this module you'll be rebuilding an existing application, starting from scratch. The application is called `HackYourTemperature` and you can find it here: [HackYourTemperature](https://hackyourtemperature.herokuapp.com/). +In this module you'll be building the simplest of API's, starting from scratch. Each week you'll be building a certain part of it. This week we'll get started with creating a web server, using [Express.js](https://expressjs.com/). Inside of the `hackyourtemperature` folder: @@ -61,7 +61,7 @@ Each week you'll be building a certain part of it. This week we'll get started w After writing all this code you can verify that it's working by running `node server.js` from the Command Line and checking your browser at `http://localhost:3000`. The page should display the message `hello from backend to frontend!`. -### 4.1 Adding a POST request +### 5.1 Adding a POST request In this part we'll add another endpoint, with a `POST` method. @@ -74,11 +74,11 @@ Test out your work using Postman and make sure that any time you submit somethin If you are tired of constantly restarting your server, google the `nodemon` package to see if that will be useful for you! -## **SUBMIT YOUR HOMEWORK!** +## **Submit your assignment!** -After you've finished your todo list it's time to show us what you got! Have a look at the following [guide](../hand-in-homework-guide.md) to see how it's done. +After you've finished your todo list it's time to show us what you got! Have a look at the following [guide](../hand-in-assignments-guide.md) to see how it's done. -The homework that needs to be submitted is the following: +The assignments that needs to be submitted is the following: 1. Project: HackYourTemperature I diff --git a/week1/README.md b/week1/README.md index 625a8646e..373338d13 100644 --- a/week1/README.md +++ b/week1/README.md @@ -17,9 +17,9 @@ These are the topics for week 1: 1. [How does the internet work?](https://study.hackyourfuture.net/#/the-internet/) -## 0. Video's +## Videos -Your teacher Andrej has made some videos for this week's material that complements the reading material. You can find them here: [Videos 1 - 6](https://www.youtube.com/playlist?list=PLVYDhqbgYpYXpc_l_Vlj8yz3LjgkkWXnn) (up to and including the Express example) +Your mentor Andrej has made some videos for this week's material that complements the reading material. You can find them here: [Videos 1 - 6](https://www.youtube.com/playlist?list=PLVYDhqbgYpYXpc_l_Vlj8yz3LjgkkWXnn) (up to and including the Express example) HYF Video @@ -29,7 +29,7 @@ This week we are going to have our first meeting with the backend and learn how Let's first take a broad look at what the term backend really means by reading up on it [here](https://study.hackyourfuture.net/#/software-development/backend.md). A common used term when looking at the frontend/backend ecosystem is the client-server model, read up about that [here](https://study.hackyourfuture.net/#/definitions/client-server-model.md). It would also be good to dive a little deeper into the communication protocol that is used in the internet by looking into the [http protocol](https://study.hackyourfuture.net/#/the-internet/http.md) and the [http methods](https://study.hackyourfuture.net/#/the-internet/http-methods) that are used to communicate certain actions. -Now that we have gotten the big picture it is time to see how we can build these things. To be able to use JavaScript on the backend, Node.js was created. Have a good look at what Node.js is [here](https://study.hackyourfuture.net/#/node-js/). +Now that we have gotten the big picture, it is time to see how we can build these things. To be able to use JavaScript on the backend, Node.js was created. Have a good look at what Node.js is [here](https://study.hackyourfuture.net/#/node-js/). In the Node.js page you have read about different modules (or packages as some call them) and we will be using one of these called **Express.js** with which we can easily create web servers. The express.js package has become an industry standard that is widely used. Have a look what it does [here](https://study.hackyourfuture.net/#/node-js/express-js). @@ -37,7 +37,7 @@ Lastly, testing your API's without a frontend is a little cumbersome. Have a loo ## Extra reading -If you have time left over this week (or any week this module) then it is a good idea to look at the topic of 'the internet' and learn how it works [here](https://study.hackyourfuture.net/#/the-internet/). This will be valuable when you get into the more complex applications and may also help your understanding of the big picture. +If you have time left over this week (or any week this module), then it is a good idea to look at the topic of 'the internet' and learn how it works [here](https://study.hackyourfuture.net/#/the-internet/). This will be valuable when you get into the more complex applications and may also help your understanding of the big picture. ## Finished? diff --git a/week2/MAKEME.md b/week2/MAKEME.md index cd79b22ad..dcb4d07c7 100644 --- a/week2/MAKEME.md +++ b/week2/MAKEME.md @@ -1,4 +1,4 @@ -# Homework Node.js Week 2 +# Assignments Node.js Week 2 ## Todo List @@ -6,7 +6,8 @@ 2. Practice exercises 3. PROJECT: HackYourTemperature II 4. Code alongs -5. Optional: Side project ideas +5. Career Training 2 (If not completed yet) +6. Optional: Side project ideas ## **1. Prep exercises** @@ -24,7 +25,7 @@ Inside of your `Node.js` fork, go to the folder `week2`. Inside of that folder, So far you've build a basic web server. We've loaded in the necessary modules. We have an `end point`, which is `/`. We have activated the server, by `listening` to a port number. And we have created a `POST` request to handle input from the user. -This week's homework we will expand on that, in 2 parts: +This week's assignments we will expand on that, in 2 parts: 1. We will connect our API to an external API to grab the data we want. 2. We are going to add tests to our API to ensure that it works as intended. @@ -137,23 +138,30 @@ Enjoy! - [Ebook Sales Application](https://www.youtube.com/watch?v=QT3_zT97_1g) -## **5. Optional: Side project ideas** +## **5. Career Training 2 (If not completed yet)** + +Remember that the Career Training 2 session is coming up (check your class channel on slack for the exact date). Before the session make sure you have: + +- Read the whole [‘Interview Preparation’ Repo](https://github.com/HackYourFuture/interviewpreparation). +- Done the assignments: make a copy of [this file](https://docs.google.com/document/u/2/d/114rTGS4eG6tpkrMAyVIdvgTrnpmkRL6ax_smkw1B0HI/copy) and submit your answers to the team [here](https://hackyourfuture.typeform.com/to/s6zYAugm). + +## **6. Optional: Side project ideas** > A part of the HackYourFuture curriculum is to work on as many side projects as you can throughout the time you have. This is a nice way to add extra knowledge to your arsenal and show in your CV that you are motivated to learn new technologies. There are plenty of people available to help you out in the `#get-help` channel on Slack so definitely make use of that! Have a look at the [hyf_projects repo](https://github.com/HackYourFuture/hyf_projects/blob/main/README.md#project-2-a-try-out-application) for more details. -### 5.1 Document your API! +### 6.1 Document your API! When using API's in the `Using API's` module you will have noticed that those API's all have extensive documentation on how to use it. As developers like to build tools for everything there are quite a few good tools to semi-automatically document your API from your code! Saves a lot of work and makes sure that you don't forget to update the documentation if the code changes! Add automatic documentation to your API by using one of these tools (Swagger, apiDoc or docbox)! -### 5.2 Web Sockets +### 6.2 Web Sockets It is becoming normal that all webpages automatically refresh whenever there is new data available. Think about the live news feeds that tell you when there is a new item, or that there is a new message on twitter. This is all implemented using Web Sockets, where you as a programmer can set up a link between your page and the server. Have a go by building a simple full stack chat application with an express websocket server! -### 5.3 GraphQL +### 6.3 GraphQL We focused solely on the REST way of building an API, but there is a different way called `GraphQL`. This allows the frontend to define in their query the data that they want to get back. Very cool, but also quite complex. If you are up for a challenge, try to recreate your project using GraphQL (`express-graphql` package is probably the easiest way)! @@ -161,9 +169,9 @@ We focused solely on the REST way of building an API, but there is a different w After you've finished your todo list it's time to show us what you got! Upload all your files to your forked repository (same as week 1). Then make a pull request to it. -If you need a refresher, take a look at the following [guide](../hand-in-homework-guide.md) to see how it's done. +If you need a refresher, take a look at the following [guide](../hand-in-assignments-guide.md) to see how it's done. -The homework that needs to be submitted is the following: +The assignments that needs to be submitted is the following: 1. Project: HackYourTemperature II diff --git a/week2/README.md b/week2/README.md index 996786b6c..144da75b8 100644 --- a/week2/README.md +++ b/week2/README.md @@ -10,13 +10,12 @@ 3. [Making use of other APIs](https://study.hackyourfuture.net/#/node-js/consuming-apis.md) - How to consume an external API? - Example of middleware -4. [Automated API testing](https://study.hackyourfuture.net/#/testing/api-testing.md) - - [Postman](https://www.postman.com/use-cases/api-testing-automation/) - - [supertest](https://www.npmjs.com/package/supertest) +4. Career Training II: [Interview preparation](https://github.com/HackYourFuture/interviewpreparation) + ## 0. Video Lectures -Your teacher Andrej has made video lectures for this week's material that complements the reading material. You can find them here: [Videos 7 - 11](https://www.youtube.com/playlist?list=PLVYDhqbgYpYXpc_l_Vlj8yz3LjgkkWXnn) (up to and including Middleware) +Your mentor Andrej has made video lectures for this week's material that complements the reading material. You can find them here: [Videos 7 - 10](https://www.youtube.com/playlist?list=PLVYDhqbgYpYXpc_l_Vlj8yz3LjgkkWXnn) HYF Video @@ -31,11 +30,20 @@ You might have noticed that the four CRUD actions nicely align with the HTTP met 3. Update -> PUT 4. Delete -> DELETE -Having covered these terms we can now look into one of the most common API architectures, the REST API. Have a look at the explanation of this design [here](https://study.hackyourfuture.net/#/the-internet/designing-apis.md). +Having covered these terms, we can now look into one of the most common API architectures, the REST API. Have a look at the explanation of this design [here](https://study.hackyourfuture.net/#/the-internet/designing-apis.md). We will also look into enhancing your API. One thing to keep in mind that your own API can make use of other API's for certain functionality! In fact, this happens all the time and is a great way to split the separation of concerns. Have a look at how this works [here](https://study.hackyourfuture.net/#/node-js/consuming-apis.md). -Lastly, it is time to learn how to automate the testing of our API's. This can be done in Postman using [automated testsuites](https://www.postman.com/use-cases/api-testing-automation/) but we are going to do it using code, similar to unit testing learned in JavaScript. Have a look [here](https://study.hackyourfuture.net/#/testing/api-testing.md) on how to do that using the [supertest](https://www.npmjs.com/package/supertest) library. +## Career Training II: interview preparation + +It is time to start practicing interviews as it is a crucial part of the hiring process. The [interview preparation repository](https://github.com/HackYourFuture/interviewpreparation) goes over the different types of interviews you will go through with companies as well as the ones that you will have to endure during the curriculum. + +### Career Training II: planning + +You don't have to do all of this immediately. This week you will receive a message from Giuseppina in your cohort channel to schedule the Career Training II session. _Before_ that session, make sure to have: + +- Read the whole [‘Interview Preparation’ Repo](https://github.com/HackYourFuture/interviewpreparation). +- Done the assignments: make a copy of [this file](https://docs.google.com/document/u/2/d/114rTGS4eG6tpkrMAyVIdvgTrnpmkRL6ax_smkw1B0HI/copy) and submit your answers to the team [here](https://hackyourfuture.typeform.com/to/s6zYAugm). ## Finished? diff --git a/week2/practice-exercises/1-joke-api/README.md b/week2/practice-exercises/1-joke-api/README.md index 669b757dd..96a54f3b8 100644 --- a/week2/practice-exercises/1-joke-api/README.md +++ b/week2/practice-exercises/1-joke-api/README.md @@ -2,7 +2,7 @@ Did you know that there is an API for Chuck Norris jokes? That's incredible, right!? -Write a small JavaScript function that calls this API [The Internet Chuck Norris Databases](http://www.icndb.com/api/) and returns a random joke. You'll be using the `node` command to execute the JavaScript file. To `GET` a random joke inside the function, use the API: http://www.icndb.com/api/ (see `node-fetch`). Make use of `async/await` and `try/catch`. +Write a small JavaScript function that calls this API [The Internet Chuck Norris Databases](http://www.icndb.com/api/) and returns a random joke. You'll be using the `node` command to execute the JavaScript file. To `GET` a random joke inside the function, use the API: https://api.chucknorris.io/jokes/random (see `node-fetch`). Make use of `async/await` and `try/catch`. Hints: diff --git a/week2/practice-exercises/3-party-time/README.md b/week2/practice-exercises/2-party-time/README.md similarity index 100% rename from week2/practice-exercises/3-party-time/README.md rename to week2/practice-exercises/2-party-time/README.md diff --git a/week2/practice-exercises/3-party-time/script.js b/week2/practice-exercises/2-party-time/script.js similarity index 100% rename from week2/practice-exercises/3-party-time/script.js rename to week2/practice-exercises/2-party-time/script.js diff --git a/week3/LESSONPLAN.md b/week3/LESSONPLAN.md new file mode 100644 index 000000000..e3ec62c16 --- /dev/null +++ b/week3/LESSONPLAN.md @@ -0,0 +1,17 @@ +# Node.js Week 3 (Lesson Plan) + +## Agenda + +1. Authentication & Authorisation +2. User registration and using bcrypt to store passwords. +3. Login, protected endpoints and logout +4. Testing + +## Core concepts + +FIRST HALF (12.00 - 13.30) + +**Example** + +**Exercise** + diff --git a/week3/MAKEME.md b/week3/MAKEME.md new file mode 100644 index 000000000..73afa3def --- /dev/null +++ b/week3/MAKEME.md @@ -0,0 +1,39 @@ +# Assignments Node.js Week 3 + +## Todo List + +1. Prep exercises +2. Practice exercises +3. Optional: Side project ideas + +## **1. Prep exercises** + +> Prep exercises are exercises that you should work on _before_ the session on Sunday. These are a little more difficult or show an important concept and as such are a great exercise to talk about with your mentor. Have a solution ready by Sunday as you may be asked to show what you did. + +Inside your `Node.js` fork, go to the folder `week3`. Inside of that folder, navigate to `/prep-exercises`. For each exercise, you will find a separate folder. The `README` explains what needs to be done. There will also be some questions at the bottom to think about. Go through them _before_ the session on Sunday as it will be covered then. + +## **2. Practice exercises** + +Inside of your `Node.js` fork, go to the folder `week3`. Inside of that folder, navigate to `/practice-exercises`. For each exercise, you will find a separate folder. The `README` explains what needs to be done. Go through them to practice concepts that you have learned about! + + +## **3. Optional: Side project ideas** + +> A part of the HackYourFuture curriculum is to work on as many side projects as you can throughout the time you have. This is a nice way to add extra knowledge to your arsenal and show in your CV that you are motivated to learn new technologies. There are plenty of people available to help you out in the `#get-help` channel on Slack so definitely make use of that! Have a look at the [hyf_projects repo](https://github.com/HackYourFuture/hyf_projects/blob/main/README.md#project-2-a-try-out-application) for more details. + +### 3.1 Document your API! + +When using API's in the `Using API's` module you will have noticed that those API's all have extensive documentation on how to use it. As developers like to build tools for everything there are quite a few good tools to semi-automatically document your API from your code! Saves a lot of work and makes sure that you don't forget to update the documentation if the code changes! + +Add automatic documentation to your API by using one of these tools (Swagger, apiDoc or docbox)! + +### 3.2 Web Sockets + +It is becoming normal that all webpages automatically refresh whenever there is new data available. Think about the live news feeds that tell you when there is a new item, or that there is a new message on twitter. This is all implemented using Web Sockets, where you as a programmer can set up a link between your page and the server. + +Have a go by building a simple full stack chat application with an express websocket server! + +### 3.3 GraphQL + +We focused solely on the REST way of building an API, but there is a different way called `GraphQL`. This allows the frontend to define in their query the data that they want to get back. Very cool, but also quite complex. If you are up for a challenge, try to recreate your project using GraphQL (`express-graphql` package is probably the easiest way)! + diff --git a/week3/README.md b/week3/README.md new file mode 100644 index 000000000..9096b3bba --- /dev/null +++ b/week3/README.md @@ -0,0 +1,41 @@ +# Reading Material Node.js Week 3 + +## Agenda + +1. [What is authentication?](https://study.hackyourfuture.net/#/node-js/authentication.md) + +2. New user registration + - [Adding users to our application](https://study.hackyourfuture.net/#/node-js/user-registration.md) + - [How to securely store user passwords](https://study.hackyourfuture.net/#/node-js/storing-passwords.md) + +3. [Session management](https://study.hackyourfuture.net/#/node-js/session-management) + - Login and session tokens + - The `Authorization` header + - Protected endpoints + - Logout + +4. [JSON Web Tokens](https://study.hackyourfuture.net/#/node-js/jwt-tokens.md) + +5. [Automated API testing](https://study.hackyourfuture.net/#/testing/api-testing.md) + - [Postman](https://www.postman.com/automated-testing/) + - [supertest](https://www.npmjs.com/package/supertest) + + +## Week goals + +This week we are going to learn about one of the most common tasks for any multi user application - `Authentication`. User authentication consists of new user registration, login, logout and identifying the currently logged in user in our API. + +You may have noticed a common trend when visiting websites that require you to sign up: + +1. **Registration** - creating a new user +2. **Login** - sending your credentials to enter the website. +3. **Accessing protected resources** - getting access to a special place in the website that only you can access (ex: shopping card, profile page) +4. **Logout** - Stop using the website. + +We will learn how to implement user registration and securely store user passwords. We will also learn how to implement a login endpoint and check if the provided username / password combination is correct. Lastly, we will implement a special endpoint that can be only accessible to a user who previously logged in. + +Lastly, it is time to learn how to automate the testing of our API's. This can be done in Postman using [automated testsuites](https://www.postman.com/use-cases/api-testing-automation/) but we are going to do it using code, similar to unit testing learned in JavaScript. Have a look [here](https://study.hackyourfuture.net/#/testing/api-testing.md) on how to do that using the [supertest](https://www.npmjs.com/package/supertest) library. + +## Finished? + +Are you finished with going through the materials? High five! If you feel ready to get practical, click [here](./MAKEME.md). diff --git a/week2/practice-exercises/2-authentication/README.md b/week3/practice-exercises/1-basic-authentication/README.md similarity index 98% rename from week2/practice-exercises/2-authentication/README.md rename to week3/practice-exercises/1-basic-authentication/README.md index 1a84fe902..6e2ef4f29 100644 --- a/week2/practice-exercises/2-authentication/README.md +++ b/week3/practice-exercises/1-basic-authentication/README.md @@ -1,4 +1,4 @@ -# Authentication +# Basic Authentication So far all the APIs we used would happily respond to any request. In reality, most APIs hold sensitive information that should not be accessible for everyone. diff --git a/week2/practice-exercises/2-authentication/script.js b/week3/practice-exercises/1-basic-authentication/script.js similarity index 100% rename from week2/practice-exercises/2-authentication/script.js rename to week3/practice-exercises/1-basic-authentication/script.js diff --git a/week3/practice-exercises/2-bcrypt-examples/README.md b/week3/practice-exercises/2-bcrypt-examples/README.md new file mode 100644 index 000000000..81c99979c --- /dev/null +++ b/week3/practice-exercises/2-bcrypt-examples/README.md @@ -0,0 +1,3 @@ +# Bcrypt experiments + +TBD \ No newline at end of file diff --git a/week3/practice-exercises/3-jwt-tokens/README.md b/week3/practice-exercises/3-jwt-tokens/README.md new file mode 100644 index 000000000..b2c8165be --- /dev/null +++ b/week3/practice-exercises/3-jwt-tokens/README.md @@ -0,0 +1,3 @@ +# JWT Token experiments + +TBD \ No newline at end of file diff --git a/week3/prep-exercise/README.md b/week3/prep-exercise/README.md new file mode 100644 index 000000000..6743dc8ee --- /dev/null +++ b/week3/prep-exercise/README.md @@ -0,0 +1,122 @@ +# Server Prep exercise week 3 + +## Goals + +In this exercise, you will build a secure authentication and authorization system using Node.js and Express.js with four main endpoints: `register`, `login`, `getProfile`, and `logout`. The system will utilize JWT (JSON Web Tokens) for managing user sessions. + +Files to be modified are located in the `server` folder. + +This will allow you to learn and practice using NodeJs and ExpressJs: + + - Securing your application with authentication and authorization principles + - Implement a standard API for users management with register, login, getProfile, and logout + - Managing user sessions using JWT (JSON Web Tokens) + +## Requirements + +You need to implement all those endpoints + +**Note:** We provide a helper to store your users so you can focus on learning the security part. + Please read more in [the next section](#database-helper) + +1. Register Endpoint: + + - Implement a `POST` endpoint `/auth/register` that allows users to register with a username and password. + - Validate the request body to ensure it includes a username and password. + - Hash the user's password using `bcrypt` before storing it in memory. + - Return a success message along with the user's ID and username upon successful registration, format: `{id: , username: }` + - In case of any errors along the way, return an appropriate error message (format: `{message: }`) with a corresponding status code in the 40x range. + +2. Login Endpoint: + + - Create a `POST` endpoint `/auth/login` that allows users to log in with their registered credentials. + - Verify the user's credentials by comparing the hashed password stored in memory. + - If authentication succeeds, generate a JWT containing the user's ID and sign it with a secret key. + - Return the JWT token to the client upon successful login, format: `{token: }` with status code 201. + - In case of any errors along the way, return an appropriate error message (format: `{message: }`) with a corresponding status code in the 40x range. + +3. Get Profile Endpoint: + + - Implement a `GET` endpoint `/auth/profile` that allows authenticated users to retrieve their profile information. + - Extract the JWT token from the Authorization header. + - Verify the JWT token and decode the payload to retrieve the user's ID. + - Retrieve the user's profile information from memory using the decoded user ID. + - Return a message with the user's username upon successful profile retrieval. + - In case of any errors along the way, return an appropriate error message (format: `{message: }`) with a status code 401 (Unauthorized). + +4. Logout Endpoint: + + - Create a `POST` endpoint `/auth/logout` that allows users to logout and invalidate their JWT token. + - No server-side token invalidation is required; the client should handle token deletion. + - Return a success response with a status code indicating successful logout (e.g., 204 No Content). + +## Database helper + +We understand there is a lot going on this week. To help you focus on user management, JWT and security, we decided to give you a small `database` tool. + +In [`users.js`](./users.js) you will find few lines that has been already added for you: + +```javascript +import newDatabase from './database.js' + +// Change this boolean to true if you wish to keep your +// users between restart of your application +const isPersistent = true +const database = newDatabase({isPersistent}) +``` + +### To store something + +To store something in the database you can use `database.create` + +**Important:** `database.create` will create an `id` for you + +```javascript +const theObjectIWouldLikeToStore = { + some: "object with one key" +} + +const storedObject = database.create(theObjectIWouldLikeToStore) + +console.log(storedObject) +// { +// some: "object with one key", +// id: '6a9252f7-d74a-4c6f-8076-dac277549e9b' +// } +``` + +### To get something from the database + +You can only get something by `id` using `database.getById` + +It will return the first object it finds with the passed `id` or it will return `undefined` + +```javascript +const storedObject = database.getById('6a9252f7-d74a-4c6f-8076-dac277549e9b') +// { +// some: "object with one key", +// id: '6a9252f7-d74a-4c6f-8076-dac277549e9b' +// } + +const notFoundObject = database.getById('NOT-A-VALID-ID') +// undefined +``` + + + + +## Client (optional) + +While you can test the endpoints of your API with Postman and/or by creating unit tests with Jest and Supertest, we have also provided a fully functional demo front-end application that demonstrates how a web token based authentication system might be used from the front-end side. The demo front-end resides in the `client` folder and is statically served by the backend. The client expects an API that meets the specification as outlined above. + +The client allows you to register, login and logout. After logging in, it uses the received JWT token to fetch the profile of the logged-in user and shows it on its home page. If this fetch fails, e.g. due to an expired token, the user is redirected to the login page. + +Upon logging in, the client stores the JWT token in `localStorage`. When the client starts it tries to load this token from `localStorage`. If a token was found it try to load the client's home page directly. This may fail if the token is expired as mentioned earlier in which case the login page is loaded. If no token was found in `localStorage` at client startup the login page is loaded directly. + +When logging out, the token is removed from `localStorage` and the user is redirected to the login page. + +The process is illustrated in the diagram below. + +![client-date-diagram](./assets/client-state-diagram.png) + +The client code logs debug information in the browser console. This may help you to follow the application flow as you navigate through its pages. diff --git a/week3/prep-exercise/assets/client-state-diagram.drawio b/week3/prep-exercise/assets/client-state-diagram.drawio new file mode 100644 index 000000000..6d4d7dbcb --- /dev/null +++ b/week3/prep-exercise/assets/client-state-diagram.drawio @@ -0,0 +1,114 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/week3/prep-exercise/assets/client-state-diagram.png b/week3/prep-exercise/assets/client-state-diagram.png new file mode 100644 index 000000000..8788af4c5 Binary files /dev/null and b/week3/prep-exercise/assets/client-state-diagram.png differ diff --git a/week3/prep-exercise/client/index.html b/week3/prep-exercise/client/index.html new file mode 100644 index 000000000..6194498c8 --- /dev/null +++ b/week3/prep-exercise/client/index.html @@ -0,0 +1,36 @@ + + + + + + + + + + + + Codestin Search App + + + + + + + + + + + +
    + + + diff --git a/week3/prep-exercise/client/public/hyf.png b/week3/prep-exercise/client/public/hyf.png new file mode 100644 index 000000000..f009198ac Binary files /dev/null and b/week3/prep-exercise/client/public/hyf.png differ diff --git a/week3/prep-exercise/client/public/style.css b/week3/prep-exercise/client/public/style.css new file mode 100644 index 000000000..44fa17b81 --- /dev/null +++ b/week3/prep-exercise/client/public/style.css @@ -0,0 +1,25 @@ +/* Adapted from: https://gist.github.com/osfx/94bc9084a0484ef2ee3d */ + +.auth-panel { + margin-top: 100px; + padding: 0px; +} + +.auth-submit-btn { + float: right; + margin-bottom: 10px; + margin-right: 10px; +} + +.auth-title { + /* background-color: #2bbbad; + color: white; */ + padding: 8px; + margin-top: 0px; +} + +form { + padding: 0px; + border-radius: 3px; + box-sizing: border-box; +} diff --git a/week3/prep-exercise/client/src/app.js b/week3/prep-exercise/client/src/app.js new file mode 100644 index 000000000..11402792f --- /dev/null +++ b/week3/prep-exercise/client/src/app.js @@ -0,0 +1,21 @@ +import createHomePage from './pages/homePage.js'; +import createLoginPage from './pages/loginPage.js'; +import loadPage from './util/loadPage.js'; +import logger from './util/logger.js'; +import { getToken } from './util/tokenUtils.js'; +import initializeState from './util/initializeState.js'; + +function loadApp() { + // Set the desired log level + logger.setLevel('debug'); + + const state = initializeState(); + + if (state.token) { + loadPage(createHomePage, state); + } else { + loadPage(createLoginPage, state); + } +} + +window.addEventListener('load', loadApp); diff --git a/week3/prep-exercise/client/src/initialState.js b/week3/prep-exercise/client/src/initialState.js new file mode 100644 index 000000000..71b8a37ef --- /dev/null +++ b/week3/prep-exercise/client/src/initialState.js @@ -0,0 +1,3 @@ +const initialState = { error: null, token: null }; + +export default initialState; diff --git a/week3/prep-exercise/client/src/pages/homePage.js b/week3/prep-exercise/client/src/pages/homePage.js new file mode 100644 index 000000000..6ce0d4035 --- /dev/null +++ b/week3/prep-exercise/client/src/pages/homePage.js @@ -0,0 +1,71 @@ +import loadPage from '../util/loadPage.js'; +import logger from '../util/logger.js'; +import fetchAndLog from '../util/fetchAndLog.js'; +import { removeToken } from '../util/tokenUtils.js'; +import createHomeView from '../views/homeView.js'; +import createLoginPage from './loginPage.js'; +import initializeState from '../util/initializeState.js'; + +function createHomePage(state) { + const updateView = (updates) => { + state = { ...state, ...updates }; + logger.debug('state', state); + view.update(state); + }; + + const onLogout = async () => { + removeToken(); + + // reset state + state = initializeState(); + + try { + const response = await fetchAndLog('/auth/logout', { + method: 'POST', + }); + + if (!response.ok) { + throw new Error(`Logout failed. Reason: HTTP ${response.status}`); + } + + loadPage(createLoginPage, state); + } catch (error) { + state = { ...state, error: error.message }; + updateView(state); + } + }; + + const getProfile = async () => { + try { + const response = await fetchAndLog('/auth/profile', { + method: 'GET', + headers: { + Authorization: `Bearer ${state.token}`, + }, + }); + + const data = await response.json(); + + if (!response.ok) { + state = { ...state, error: data.message }; + logger.debug('state', state); + removeToken(); + state = initializeState(); + loadPage(createLoginPage, state); + return; + } + + updateView({ profile: data.message }); + } catch (error) { + state = { ...state, error: error.message }; + updateView(state); + } + }; + + const view = createHomeView({ onLogout }); + getProfile(); + + return view; +} + +export default createHomePage; diff --git a/week3/prep-exercise/client/src/pages/loginPage.js b/week3/prep-exercise/client/src/pages/loginPage.js new file mode 100644 index 000000000..42c93b0c0 --- /dev/null +++ b/week3/prep-exercise/client/src/pages/loginPage.js @@ -0,0 +1,50 @@ +import loadPage from '../util/loadPage.js'; +import logger from '../util/logger.js'; +import { putToken } from '../util/tokenUtils.js'; +import fetchAndLog from '../util/fetchAndLog.js'; +import createLoginView from '../views/loginView.js'; +import createRegisterPage from './registerPage.js'; +import createHomePage from './homePage.js'; + +function createLoginPage(state) { + const updateView = (updates) => { + state = { ...state, ...updates }; + logger.debug('state', state); + view.update(state); + }; + + const onSubmit = async (username, password) => { + try { + const response = await fetchAndLog('/auth/login', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.message); + } + + putToken(data.token); + state = { ...state, token: data.token, error: null }; + + loadPage(createHomePage, state); + } catch (error) { + state = { ...state, error: error.message }; + updateView(state); + } + }; + + const onRegister = () => { + loadPage(createRegisterPage, state); + }; + + const view = createLoginView({ onSubmit, onRegister }); + + return view; +} + +export default createLoginPage; diff --git a/week3/prep-exercise/client/src/pages/registerPage.js b/week3/prep-exercise/client/src/pages/registerPage.js new file mode 100644 index 000000000..4ea4f9ec6 --- /dev/null +++ b/week3/prep-exercise/client/src/pages/registerPage.js @@ -0,0 +1,48 @@ +import loadPage from '../util/loadPage.js'; +import logger from '../util/logger.js'; +import fetchAndLog from '../util/fetchAndLog.js'; +import createRegisterView from '../views/registerView.js'; +import createRegisterSuccessPage from './registerSuccessPage.js'; +import createLoginPage from './loginPage.js'; + +function createRegisterPage(state) { + const updateView = (updates) => { + state = { ...state, ...updates }; + logger.debug('state', state); + view.update(state); + }; + + const onSubmit = async (username, password) => { + try { + const response = await fetchAndLog('/auth/register', { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify({ username, password }), + }); + + const data = await response.json(); + if (!response.ok) { + throw new Error(data.message); + } + + logger.debug('response', data); + + loadPage(createRegisterSuccessPage, state); + } catch (error) { + state = { ...state, error: error.message }; + updateView(state); + } + }; + + const onLogin = () => { + loadPage(createLoginPage, state); + }; + + const view = createRegisterView({ onSubmit, onLogin }); + + return view; +} + +export default createRegisterPage; diff --git a/week3/prep-exercise/client/src/pages/registerSuccessPage.js b/week3/prep-exercise/client/src/pages/registerSuccessPage.js new file mode 100644 index 000000000..1980cf39b --- /dev/null +++ b/week3/prep-exercise/client/src/pages/registerSuccessPage.js @@ -0,0 +1,16 @@ +import loadPage from '../util/loadPage.js'; +import logger from '../util/logger.js'; +import createRegisterSuccessView from '../views/registerSuccessView.js'; +import createLoginPage from './loginPage.js'; + +function createRegisterSuccessPage(state) { + const onLogin = () => { + loadPage(createLoginPage, state); + }; + + const view = createRegisterSuccessView({ onLogin }); + + return view; +} + +export default createRegisterSuccessPage; diff --git a/week3/prep-exercise/client/src/util/fetchAndLog.js b/week3/prep-exercise/client/src/util/fetchAndLog.js new file mode 100644 index 000000000..c10a7c796 --- /dev/null +++ b/week3/prep-exercise/client/src/util/fetchAndLog.js @@ -0,0 +1,19 @@ +import logger from './logger.js'; + +/** + * A wrapper function for `fetch` with before and after logging + * @param {*} url Same as for fetch + * @param {*} options Same as for fetch + * @returns Same as for fetch + */ +async function fetchAndLog(url, options = { method: 'GET' }) { + const { method, ...rest } = options; + logger.debug('fetch', `${method} ${url}`, ...Object.values(rest)); + + const response = await fetch(url, options); + + logger.debug('status', response.status); + return response; +} + +export default fetchAndLog; diff --git a/week3/prep-exercise/client/src/util/getViewIds.js b/week3/prep-exercise/client/src/util/getViewIds.js new file mode 100644 index 000000000..1239de048 --- /dev/null +++ b/week3/prep-exercise/client/src/util/getViewIds.js @@ -0,0 +1,15 @@ +/** + * Get all child elements with an `id` attribute, starting from `root`. + * @param {HTMLElement} root The root element to start from. + * @returns An object with `id` as key and an element reference as value. + */ +function getViewIds(root) { + const elementsWithIds = Array.from(root.querySelectorAll('[id]')); + const dom = {}; + for (const elem of elementsWithIds) { + dom[elem.id] = elem; + } + return dom; +} + +export default getViewIds; diff --git a/week3/prep-exercise/client/src/util/initializeState.js b/week3/prep-exercise/client/src/util/initializeState.js new file mode 100644 index 000000000..8bc3d80a0 --- /dev/null +++ b/week3/prep-exercise/client/src/util/initializeState.js @@ -0,0 +1,19 @@ +import initialState from '../initialState.js'; +import { getToken } from './tokenUtils.js'; + +/** + * Initialize the state, including token if present from localStorage. + * @returns A an initialized state object + */ +function initializeState() { + const state = { ...initialState }; + + const token = getToken(); + if (token) { + state.token = token; + } + + return state; +} + +export default initializeState; diff --git a/week3/prep-exercise/client/src/util/loadPage.js b/week3/prep-exercise/client/src/util/loadPage.js new file mode 100644 index 000000000..52a04838a --- /dev/null +++ b/week3/prep-exercise/client/src/util/loadPage.js @@ -0,0 +1,18 @@ +import logger from './logger.js'; + +/** + * Load an application page + * @param {*} pageFactoryFn Factory function for the page to load + * @param {*} state State object to be passed to the page + */ +function loadPage(pageFactoryFn, state) { + logger.debug('loadPage', pageFactoryFn.name.replace('create', '')); + logger.debug('state', state); + const appRoot = document.getElementById('app-root'); + appRoot.innerHTML = ''; + + const page = pageFactoryFn(state); + appRoot.appendChild(page.root); +} + +export default loadPage; diff --git a/week3/prep-exercise/client/src/util/logger.js b/week3/prep-exercise/client/src/util/logger.js new file mode 100644 index 000000000..7da6b5c93 --- /dev/null +++ b/week3/prep-exercise/client/src/util/logger.js @@ -0,0 +1,79 @@ +/** + * This file is provided ready-made for use in your application by HackYourFuture. + * There should be no reason to make any changes to this file. + */ + +// Log levels in increasing severity +const LEVELS = ['silly', 'debug', 'info', 'warn', 'error', 'fatal', 'none']; + +/** + * Create a logger object. + * @returns + */ +function logger() { + let minLevel = LEVELS.length - 1; + + // Check the requested level against the minimum level + const isMinLevel = (level) => LEVELS.indexOf(level) >= minLevel; + + // The function that does the actual logging. + const log = (level, label, ...args) => { + if (!isMinLevel(level)) { + return; + } + + let logFn; + + switch (level) { + case 'warn': + logFn = console.warn; + break; + case 'info': + logFn = console.info; + break; + case 'error': + logFn = console.error; + break; + default: + logFn = console.log; + } + + logFn(`${level}: ${label} =>`, ...args); + }; + + // Return an object with convenience functions for logging at specific + // log levels. + return { + setLevel(level) { + const newLevel = LEVELS.indexOf(level); + if (newLevel !== -1) { + minLevel = newLevel; + } + }, + getLevel() { + return LEVELS[minLevel]; + }, + isMinLevel, + log, + silly(label, ...args) { + log('silly', label, ...args); + }, + debug(label, ...args) { + log('debug', label, ...args); + }, + info(label, ...args) { + log('info', label, ...args); + }, + warn(label, ...args) { + log('warn', label, ...args); + }, + error(label, ...args) { + log('error', label, ...args); + }, + fatal(label, ...args) { + log('fatal', label, ...args); + }, + }; +} + +export default logger(); diff --git a/week3/prep-exercise/client/src/util/tokenUtils.js b/week3/prep-exercise/client/src/util/tokenUtils.js new file mode 100644 index 000000000..8d0c0788e --- /dev/null +++ b/week3/prep-exercise/client/src/util/tokenUtils.js @@ -0,0 +1,13 @@ +const TOKEN_NAME = 'token'; + +export function getToken() { + return localStorage.getItem(TOKEN_NAME); +} + +export function putToken(token) { + localStorage.setItem(TOKEN_NAME, token); +} + +export function removeToken() { + localStorage.removeItem(TOKEN_NAME); +} diff --git a/week3/prep-exercise/client/src/views/homeView.js b/week3/prep-exercise/client/src/views/homeView.js new file mode 100644 index 000000000..2053e45c2 --- /dev/null +++ b/week3/prep-exercise/client/src/views/homeView.js @@ -0,0 +1,53 @@ +import getViewIds from '../util/getViewIds.js'; +import createModalDialView from './modalDialogView.js'; + +function createHomeView(props) { + const root = document.createElement('div'); + root.innerHTML = String.raw` +
    +
    + + +

    +
    +
    + `; + + const modalView = createModalDialView({ title: 'Error' }); + root.append(modalView.root); + + const dom = getViewIds(root); + + const sideNavElements = root.querySelectorAll('.sidenav'); + const sideNavInstances = M.Sidenav.init(sideNavElements); + + const logoutHandler = (event) => { + event.preventDefault(); + sideNavInstances[0].close(); + props.onLogout(); + }; + + dom.logoutBtn.addEventListener('click', logoutHandler); + dom.mobileLogoutBtn.addEventListener('click', logoutHandler); + + const update = (state) => { + dom.profile.textContent = state.profile; + modalView.update(state); + }; + + return { root, update }; +} + +export default createHomeView; diff --git a/week3/prep-exercise/client/src/views/loginView.js b/week3/prep-exercise/client/src/views/loginView.js new file mode 100644 index 000000000..1575906d6 --- /dev/null +++ b/week3/prep-exercise/client/src/views/loginView.js @@ -0,0 +1,59 @@ +import getViewIds from '../util/getViewIds.js'; +import createModalDialView from './modalDialogView.js'; + +const MESSAGE_TIMEOUT_MS = 2000; + +function createLoginView(props) { + const root = document.createElement('div'); + root.innerHTML = String.raw` +
    +
    +
    +
    Login
    +
    +
    + + +
    +
    + + +
    + +
    +
    +

    Not yet registered? + + Create an account + +

    +
    +
    +
    +
    + `; + + const modalView = createModalDialView({ title: 'Login Failed' }); + root.append(modalView.root); + + const dom = getViewIds(root); + + dom.form.addEventListener('submit', (event) => { + event.preventDefault(); + props.onSubmit(dom.username.value, dom.password.value); + }); + + dom.registerLink.addEventListener('click', (event) => { + event.preventDefault(); + props.onRegister(); + }); + + const update = (state) => { + modalView.update(state); + }; + + return { root, update }; +} + +export default createLoginView; diff --git a/week3/prep-exercise/client/src/views/modalDialogView.js b/week3/prep-exercise/client/src/views/modalDialogView.js new file mode 100644 index 000000000..96d0dbdcb --- /dev/null +++ b/week3/prep-exercise/client/src/views/modalDialogView.js @@ -0,0 +1,32 @@ +function createModalDialView(props) { + const root = document.createElement('div'); + root.innerHTML = String.raw` + + + `; + + const dom = {}; + dom.modalText = root.querySelector('#modal-text'); + + const modalElements = root.querySelectorAll('.modal'); + const modalInstances = M.Modal.init(modalElements); + + const update = (state) => { + if (state.error) { + dom.modalText.textContent = state.error; + modalInstances[0].open(); + } + }; + + return { root, update }; +} + +export default createModalDialView; diff --git a/week3/prep-exercise/client/src/views/registerSuccessView.js b/week3/prep-exercise/client/src/views/registerSuccessView.js new file mode 100644 index 000000000..b5ccb4843 --- /dev/null +++ b/week3/prep-exercise/client/src/views/registerSuccessView.js @@ -0,0 +1,48 @@ +import getViewIds from '../util/getViewIds.js'; + +function createRegisterSuccessView(props) { + const root = document.createElement('div'); + root.innerHTML = String.raw` +
    +
    + + +
    + +
    +
    Registration was successful!
    +

    You can now login with your username and password.

    +
    +
    + `; + + const dom = getViewIds(root); + + const sideNavElements = root.querySelectorAll('.sidenav'); + const sideNavInstances = M.Sidenav.init(sideNavElements); + + const loginHandler = (event) => { + event.preventDefault(); + sideNavInstances[0].close(); + props.onLogin(); + }; + + dom.loginBtn.addEventListener('click', loginHandler); + dom.mobileLoginBtn.addEventListener('click', loginHandler); + + return { root }; +} + +export default createRegisterSuccessView; diff --git a/week3/prep-exercise/client/src/views/registerView.js b/week3/prep-exercise/client/src/views/registerView.js new file mode 100644 index 000000000..8812f1630 --- /dev/null +++ b/week3/prep-exercise/client/src/views/registerView.js @@ -0,0 +1,57 @@ +import getViewIds from '../util/getViewIds.js'; +import createModalDialView from './modalDialogView.js'; + +function createRegisterView(props) { + const root = document.createElement('div'); + root.innerHTML = String.raw` +
    +
    +
    +
    Register
    +
    +
    + + +
    +
    + + +
    + +
    +
    +

    Already registered? + + Login + +

    +
    +
    +
    +
    + `; + + const modalView = createModalDialView({ title: 'Registration Failed' }); + root.append(modalView.root); + + const dom = getViewIds(root); + + dom.registerForm.addEventListener('submit', (event) => { + event.preventDefault(); + props.onSubmit(dom.username.value, dom.password.value); + }); + + dom.loginLink.addEventListener('click', (event) => { + event.preventDefault(); + props.onLogin(); + }); + + const update = (state) => { + modalView.update(state); + }; + + return { root, update }; +} + +export default createRegisterView; diff --git a/week3/prep-exercise/package.json b/week3/prep-exercise/package.json new file mode 100644 index 000000000..6d4a60eeb --- /dev/null +++ b/week3/prep-exercise/package.json @@ -0,0 +1,27 @@ +{ + "name": "build-with-students", + "version": "1.0.0", + "description": "", + "main": "app.js", + "types": "module", + "scripts": { + "start": "nodemon ./server/app.js", + "demo": "nodemon ./server-demo/app.js", + "db:test": "node ./server/database.js" + }, + "type": "module", + "keywords": [], + "author": "", + "license": "ISC", + "dependencies": { + "bcrypt": "^5.1.1", + "express": "^4.18.2", + "lokijs": "^1.5.12", + "jsonwebtoken": "^9.0.2", + "uuid": "^9.0.1" + }, + "devDependencies": { + "nodemon": "^3.1.0" + } +} + diff --git a/week3/prep-exercise/server/app.js b/week3/prep-exercise/server/app.js new file mode 100644 index 000000000..a94f09af8 --- /dev/null +++ b/week3/prep-exercise/server/app.js @@ -0,0 +1,15 @@ +import express from 'express'; +// TODO Use below import statement for importing middlewares from users.js for your routes +// TODO import { ....... } from "./users.js"; + +let app = express(); + +app.use(express.json()); +// TODO: Create routes here, e.g. app.post("/register", .......) + +// Serve the front-end application from the `client` folder +app.use(express.static('client')); + +app.listen(3000, () => { + console.log('Server is running on port 3000'); +}); diff --git a/week3/prep-exercise/server/database.js b/week3/prep-exercise/server/database.js new file mode 100644 index 000000000..f7594babd --- /dev/null +++ b/week3/prep-exercise/server/database.js @@ -0,0 +1,141 @@ +/** + * Hello Student, + * + * This file is a helper file. + * Thanks to this file you do not have to care yet about how to store + * your users. + * + * HOW TO USE: Look into the README.md + * + * ## YOU DO NOT HAVE TO READ OR UNDERSTAND THIS CODE TO USE IT. + * ## Stop reading here if you just would like to use the database + * ## and focus on the register, login, getProfile, and logout :) + * ########################################################### + * ########################################################### + * ########################################################### + * ########################################################### + * ## Read After this if you intend to update this helper file. + * + * This relies on lokijs for the persistence layer and a + * simple array for the in memory one. + * + * Both makeInMemoryDb and makeNewLokiDatabase needs to return + * the same shape of object. + * Here is a typescript style signature of said object: + * + * type DbInterface = { + * create(object: T): {id: string} & T + * getById(id: string): {id: string} & T + * } + * + * lokijs: https://github.com/techfort/LokiJS + * + * You will find rudimentary tests at the end of this file, + * you can run them thanks to: + * npm run db:test + * or + * yarn db:test + */ + + +import {default as Loki} from 'lokijs' +import {v4 as uuid} from 'uuid' + +const makeInMemoryDb = () => { + const localDb = [] + + return { + create: (user) => { + const storedUser = { + ...user, + id: uuid() + } + + localDb.push(storedUser) + + return storedUser + }, + getById: (id) => { + return localDb.find(user => user.id === id) || undefined + } + } +} + +const makeNewLokiDatabase = () => { + const db = new Loki('sandbox.db'); + const users = db.addCollection('users'); + + return { + create: (user) => { + const storedUser = { + ...user, + id: uuid() + } + + users.insert(storedUser) + + return storedUser + }, + getById: (id) => { + return users.findOne({id}) || undefined + } + } +} + +/** + * Db factory + * + * @param isPersistent should it return a LokiJS based implementation or array based one? + * @returns {{ + * create: (function(*): *&{id: string}), + * getById: (function(id: string): {id: string}&*) + * }} + */ +const makeDatabase = ({isPersistent} = {isPersistent: false}) => + isPersistent ? makeNewLokiDatabase() : makeInMemoryDb() + +export default makeDatabase + +// TESTS ######################################################## + +if (process.argv[0].includes("node") && process.argv[1].includes("database.js")) { + console.log('running database tests -------------') + + // Given + const dbPersist = makeDatabase({isPersistent: true}) + const dbInMem = makeDatabase({isPersistent: false}) + + const testUser = { + name: "super", + pw: "else", + some: { + nested: "key", + and: ["an", "array"] + } + } + + // When creating user + const persistUser = dbPersist.create(testUser) + const inMemStoredUser = dbInMem.create(testUser) + + // Then + console.assert(persistUser.some.nested === testUser.some.nested, "fail to create user from persist db") + console.assert(inMemStoredUser.some.nested === testUser.some.nested, "fail to create user from in mem db") + console.assert(persistUser.id !== undefined, "persistent db returned user without id") + console.assert(inMemStoredUser.id !== undefined, "in mem user returned db without id") + + // When retrieving user + const foundPersistedUser = dbPersist.getById(persistUser.id) + const foundInMemUser = dbInMem.getById(inMemStoredUser.id) + const notFoundPersistedUser = dbPersist.getById("FAKE-ID") + const notFoundMemUser = dbPersist.getById("FAKE-ID") + + // Then + console.assert(foundPersistedUser.name === testUser.name, "retrieving user from persistent db failed") + console.assert(foundInMemUser.name === testUser.name, "retrieving user from inMem db failed") + console.assert(notFoundPersistedUser === undefined, "persistent db returned a user from an unknown ID") + console.assert(notFoundMemUser === undefined, "in mem db returned a user from an unknown ID") + + console.log('All tests performed ---------------') + console.log('No output means tests passes') +} diff --git a/week3/prep-exercise/server/users.js b/week3/prep-exercise/server/users.js new file mode 100644 index 000000000..fbf91e6c2 --- /dev/null +++ b/week3/prep-exercise/server/users.js @@ -0,0 +1,12 @@ +import newDatabase from './database.js' + +// Change this boolean to true if you wish to keep your +// users between restart of your application +const isPersistent = false +const database = newDatabase({isPersistent}) + +// Create middlewares required for routes defined in app.js +// export const register = async (req, res) => {}; + +// You can also create helper functions in this file to help you implement logic +// inside middlewares