From 0a99c40c009e6ec46919798af356fbd2b76b6ca6 Mon Sep 17 00:00:00 2001 From: Marco Richetta Date: Fri, 22 Aug 2025 19:01:53 +0200 Subject: [PATCH 01/21] move legacy nodejs week 2 to backend nodejs week1 --- courses/backend/node/README.md | 22 +- courses/backend/node/week1/README.md | 80 +++++++ courses/backend/node/week1/assignment.md | 205 ++++++++++++++++++ .../backend/node/week1/exercises/01-server.md | 47 ++++ .../backend/node/week1/exercises/02-schema.md | 35 +++ .../backend/node/week1/exercises/03-api.md | 57 +++++ .../node/week1/exercises/04-post-endpoint.md | 91 ++++++++ .../node/week1/exercises/05-get-endpoints.md | 43 ++++ .../backend/node/week1/exercises/06-auth.md | 119 ++++++++++ courses/backend/node/week1/preparation.md | 21 ++ courses/backend/node/week1/session-plan.md | 63 ++++++ 11 files changed, 781 insertions(+), 2 deletions(-) create mode 100644 courses/backend/node/week1/README.md create mode 100644 courses/backend/node/week1/assignment.md create mode 100644 courses/backend/node/week1/exercises/01-server.md create mode 100644 courses/backend/node/week1/exercises/02-schema.md create mode 100644 courses/backend/node/week1/exercises/03-api.md create mode 100644 courses/backend/node/week1/exercises/04-post-endpoint.md create mode 100644 courses/backend/node/week1/exercises/05-get-endpoints.md create mode 100644 courses/backend/node/week1/exercises/06-auth.md create mode 100644 courses/backend/node/week1/preparation.md create mode 100644 courses/backend/node/week1/session-plan.md diff --git a/courses/backend/node/README.md b/courses/backend/node/README.md index 87f28011..aabe9f91 100644 --- a/courses/backend/node/README.md +++ b/courses/backend/node/README.md @@ -1,3 +1,21 @@ -# Node +# Node.js -Coming soon +This module is part of the Backend specialism and focuses on using Node.js to build backend services, APIs, and databases. It builds on the Introduction to Backend module from Foundation with more advanced tooling, patterns, and responsibilities. + +## Contents + +| Week | Topic | Preparation | Lesson Plan | Assignment | +| ---- | ------------------------ | ----------------------------------- | ------------------------------------ | ----------------------------------- | +| 1. | Express | [Preparation](week1/preparation.md) | [Homework](week1/homework/README.md) | [Lesson plan](week1/lesson-plan.md) | +| 2. | Database connection; API | [Preparation](week2/preparation.md) | [Homework](week2/homework/README.md) | [Lesson plan](week2/lesson-plan.md) | + +## Module Learning Goals + +By the end of this module, you will be able to: + +- [ ] Build web servers with Express.js +- [ ] Design and implement APIs using HTTP methods following REST principles +- [ ] Use middlewares for authentication, logging, and validation +- [ ] Test APIs using Postman +- [ ] Use logging and debugging tools to monitor and troubleshoot applications +- [ ] Connect to databases and implement CRUD operations diff --git a/courses/backend/node/week1/README.md b/courses/backend/node/week1/README.md new file mode 100644 index 00000000..e4637f43 --- /dev/null +++ b/courses/backend/node/week1/README.md @@ -0,0 +1,80 @@ +# Session plan (Week 1) + +In this session we will focus on Express.js, which is an application framework for building webservers in an easy manner. Although we could write everything from scratch in Node, we don’t want to. In many situations a webserver is generic enough for us and we gain a lot by using something that just works and makes our lives as developers easier – as long as what we are trying to do is within the scope of Express. + +## Contents + +- [Preparation](./preparation.md) +- [Session Plan](./session-plan.md) (for mentors) +- [Assignment](./assignment.md) + +## Session Learning Goals + +By the end of this session, you will be able to: + +- [ ] Explain what Express is and describe why it is used for building backend applications. +- [ ] Implement routing in Express to handle different HTTP requests and endpoints. +- [ ] Use logging and debugging tools to monitor and troubleshoot Node.js applications. +- [ ] Apply middleware functions in Express to process requests and responses. +- [ ] Use Postman to test and debug APIs you have built. + + +TODO - Move this content somewhere else + +When writing an Express application we are registering routes on HTTP keywords (GET,POST,PUT, DELETE) and handler functions for those routes. + +The most basic express webserver looks like the following: + +```js +// We require the express package after having installed it +// via “npm i express” +import express from "express"; + +// We create an express instance and bind it to our app const +const app = express(); + +// We register the first route with the following; + +// all “GET” requests on the path “/“ +// will be handled by the function (req, res) {} +app.get("/", function (req, res) { + // the handler receives the request on “req” + // and has access to the response on “res” + + // res.send allows us to send a response to a request + res.send("hello world"); +}); + +// We start the express webserver by listening to port 3000 +app.listen(3000); +``` + +If we run this script, node will start up and run the code ending with app.listen(3000). This tells Node to bind and listen for connections on this specified host and port. + +If there is no specified host, Node will bind to localhost and the Node application will be available on . + +If you open that URL in the browser you will see “hello world” (without any styling). + +## Extensibility + +This is a very basic example of an express application. There many parameters to tweak and cases to take into consideration. +One of the first things we’d like to do in a typical Express application is to not return strings or HTML, but instead use JSON as the transport encoding format. Meaning we accept JSON in requests and we can respond with JSON formatted data. + +There are a few good extensions or middleware that is easy to plug into express and allows us to tweak and change some of the parts of the webserver. Some of these middleware extensions include body-parser, compression, cors, errorhandler - find the full list here: . + +### Relevant links + +- [Preparation](preparation.md) +- [Homework](homework/README.md) +- [Lesson plan](lesson-plan.md) + +### Express.js + +In Node.js it's possible to make a HTTP server using the native `http` module. However, this is rarely used in practice. Instead, we'll use [Express.js](https://expressjs.com/en/4x/api.html), a backend framework that can do what the `http` module does and much more (in a simpler, faster and more readable way). + +Practically speaking, what can we do with a web server like `http` or `Express`? All the magic that makes the frontend work: + +- Get and store data that comes from the frontend +- Make API calls to other services +- Secure data that comes from both the frontend and the database +- Any other type of calculation or business logic diff --git a/courses/backend/node/week1/assignment.md b/courses/backend/node/week1/assignment.md new file mode 100644 index 00000000..8c5a0a74 --- /dev/null +++ b/courses/backend/node/week1/assignment.md @@ -0,0 +1,205 @@ +# Assignment + +This homework, just like the previous week, will result in 2 pull requests: + +- A pull request for the **Warmup** - in your regular hyf-homework repository +- A pull request for the additional **Meal sharing endpoints** - in the meal-sharing repository + +In both repositories, create a `nodejs-week1` branch from `main` to work on the homework (`git checkout -b nodejs-week1` ) + +## Warmup + +For the warmup you're going to build a search engine. The search engine will have 3 routes: + +- `GET /search` +- `GET /documents/:id` +- `POST /search` + +The purpose of the search engine is to search and find documents from a file called `documents.json`. Example contents: + +```json +[ + { + "id": 1, + "name": "Used Apple Airpods", + "price": "50", + "description": "Battery life is not great" + }, + { + "id": 2, + "type": "doc", + "value": "hello world" + } +] +``` + +### Setup + +Go to `nodejs/week1` in your `hyf-homework` repo: + +```shell +npm init -y +npm pkg set type="module" +npm i express +npm i --save-dev nodemon +npm pkg set scripts.dev="nodemon app.js" +``` + +You should ensure that the `node_modules/` folder is ignored by Git: + +```shell +echo node_modules/ >> .gitignore +``` + +Create `app.js` and as a starting point you can use the following code: + +```js +import express from "express"; +const app = express(); +const port = process.env.PORT || 3000; + +// Support parsing JSON requests +app.use(express.json()); + +app.get("/", (req, res) => { + res.send("This is a search engine"); +}); + +app.listen(port, () => { + console.log(`Listening on port ${port}`); +}); +``` + +You also need to create a `documents.json` file. + +### `GET /search` + +This endpoint will accept a query parameter called `q`, short for _query_. A bit confusing to have a query parameter called q(uery) but hang in there 💪 + +- If `q` is not provided, the endpoint should return all documents. +- If `q` is provided, the endpoint should return the documents with some field that matches the value of `q`. + +Example response if we call `GET /search?q=hello`: + +```json +[ + { + "id": 2, + "type": "doc", + "value": "hello world" + } +] +``` + +### `GET /documents/:id` + +This endpoint is simple: find and respond with the document matching the `id` parameter. If there is no such document, respond with a 404 Not Found. +You can assume that the document IDs are unique so there's no need to handle duplicates. + +### `POST /search` + +This endpoint is sort of like `GET /search`. + +It also accepts a query parameter called `q` and it should behave just like `GET /search`. +But it also accepts a field called `fields` in the JSON request body. `fields` is an object where it will be possible to filter by specific fields. + +Example request: + +```text +POST /search +{ + "fields": { + "price": "50" + } +} +``` + +Response to the example request: + +```json +[ + { + "id": 1, + "name": "Used Apple Airpods", + "price": "50", + "description": "Battery life is not great" + } +] +``` + +If both `q` (query parameter) and `fields` (in body) are provided, we should respond with status 400 Bad Request and explain that both can't be provided. + +## Meal sharing endpoints + +You will continue working in the meal-sharing repository for this homework. This week you will build more endpoints, developing some [CRUD](https://www.freecodecamp.org/news/crud-operations-explained/) operations for your future meal sharing website backend functionality. + +### Routes + +In last week's homework you added routes in `/api/src/index.js`. You can just leave them there as they are. + +For this week's homework, we will add two categories of routes: meals and reservations. + +- The routes for meals will go into `/api/src/routers/meals.js` +- The reservation routes will live in `/api/src/routers/reservations.js` + +This means that we will end up having two Routers: a meals router and a reservations router. +You can read more about Express Routers [here](https://expressjs.com/en/4x/api.html#router). + +You can reference the file `/api/src/routers/nested.js` for an example, and see how it is used in `/api/src/index.js`. + +#### Meals + +| Route | HTTP method | Description | +| ---------------- | ----------- | ------------------------------- | +| `/api/meals` | GET | Returns all meals | +| `/api/meals` | POST | Adds a new meal to the database | +| `/api/meals/:id` | GET | Returns the meal by `id` | +| `/api/meals/:id` | PUT | Updates the meal by `id` | +| `/api/meals/:id` | DELETE | Deletes the meal by `id` | + +#### Reservations + +Now that you have built the basic set of endpoints for **meals**, you can get some more practice and expand your app backend by creating the same for **reservations**: + +| Route | HTTP method | Description | +| ----------------------- | ----------- | -------------------------------------- | +| `/api/reservations` | GET | Returns all reservations | +| `/api/reservations` | POST | Adds a new reservation to the database | +| `/api/reservations/:id` | GET | Returns a reservation by `id` | +| `/api/reservations/:id` | PUT | Updates the reservation by `id` | +| `/api/reservations/:id` | DELETE | Deletes the reservation by `id` | + +##### Requests + +All the `POST` or `PUT` endpoints will require a request body - the information that your database will be updated with. + +##### Responses + +All the specified `GET` routes should respond with JSON with the available columns from the associated tables. + +The `GET`, `PUT` and `DELETE` routes that include an `/:id` in the path should make sure to handle the case when the row with that ID does not exist. + +> Think about what special HTTP status code would be appropriate for that scenario. + +You are free to decide on the response for a successful `POST`, `PUT` and `DELETE` request. Some ideas: + +- Respond with an acknowledgement message: `{ "message": "Deleted meal" }` +- Respond with data from the row itself like with `GET` + +And lastly, if the `POST` request is successful, the response status code should be 201 Created, as that would indicate something was _created_. + +#### Knex + +Your usage of Knex should be getting a bit more advanced now. You will move from `knex.raw` on to different `knex` function, for example: + +- `.select`, `.from`, `.where` +- `.insert` +- `.update` +- `.del` (for deletion) + +Check out the [Knex cheatsheet](https://devhints.io/knex)! + +## Hand in homework + +Need to brush up on the homework hand-in process? +Check [this resource](https://github.com/HackYourFuture-CPH/Git/blob/main/homework-submission.md) to remember how to hand in the homework correctly! diff --git a/courses/backend/node/week1/exercises/01-server.md b/courses/backend/node/week1/exercises/01-server.md new file mode 100644 index 00000000..8469bc89 --- /dev/null +++ b/courses/backend/node/week1/exercises/01-server.md @@ -0,0 +1,47 @@ +# Server + +Create a new, separate folder somewhere on your machine: + +```shell +mkdir nodejs-week2 +code nodejs-week2 # to open the folder in VS Code +``` + +You can also reuse some other folder. Main thing to watch out for is that the folder you decide to use should ideally be empty. + +--- + +Initialize and install: + +```shell +npm init -y +npm pkg set type="module" +npm install express mysql2 knex +echo node_modules/ >> .gitignore +``` + +Create a file named `app.js` and use the following as a starting point for this exercise: + +```js +import express from "express"; +const app = express(); +const port = process.env.PORT || 3000; + +app.get("/", (req, res) => { + res.send("Hello Class!"); +}); + +app.listen(port, () => { + console.log(`Listening on port ${port}`); +}); +``` + +--- + +```shell +npm install --save-dev nodemon +npm pkg set scripts.dev="nodemon app.js" +npm run dev +``` + +Go to in your browser to verify that the server started. diff --git a/courses/backend/node/week1/exercises/02-schema.md b/courses/backend/node/week1/exercises/02-schema.md new file mode 100644 index 00000000..67a5a71a --- /dev/null +++ b/courses/backend/node/week1/exercises/02-schema.md @@ -0,0 +1,35 @@ +# Schema + +This week we will work with 2 tables: + +- A `users` table similar to week 1 but now with an added `token` column. +- A `snippets` table containing (code) snippets that belong to a user. + +Create a new database/schema `hyf_node_week2` containing the following tables: + +```sql +CREATE TABLE `users` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `confirmed_at` datetime DEFAULT NULL, + `first_name` varchar(255) NOT NULL, + `last_name` varchar(255) NOT NULL, + `email` varchar(255) NOT NULL, + `token` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`), + UNIQUE KEY `email_UNIQUE` (`email`), + UNIQUE KEY `token_UNIQUE` (`token`) +) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4; + +CREATE TABLE `snippets` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + `user_id` int unsigned NOT NULL, + `title` varchar(255) NOT NULL, + `contents` text NOT NULL, + `is_private` tinyint NOT NULL DEFAULT '1', + PRIMARY KEY (`id`), + KEY `id_idx` (`user_id`), + CONSTRAINT `id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +``` diff --git a/courses/backend/node/week1/exercises/03-api.md b/courses/backend/node/week1/exercises/03-api.md new file mode 100644 index 00000000..ec3c3cf0 --- /dev/null +++ b/courses/backend/node/week1/exercises/03-api.md @@ -0,0 +1,57 @@ +# API + +The end goal of the exercise is to implement the following routes: + +- `POST /api/snippets` to create a snippet +- `GET /api/snippets` to get a list of snippets +- `GET /api/snippets/:id` to get a single snippet + +We will create the snippet routes in a different file, `api/snippets.js`, which will export an [Express Router](https://expressjs.com/en/4x/api.html#router). + +That will look something like this: + +```js +// Contents of api/snippets.js + +import express from "express"; +const router = express.Router(); + +// GET /api/snippets +router.get("/", async (request, response) => { + // TODO +}); + +// TODO: POST /api/snippets +// TODO: GET /api/snippets/:id + +export default router; +``` + +We will also have the database connection in a separate file: + +```js +// Contents of database.js + +import knex from "knex"; + +const knexInstance = knex({ + client: "mysql2", + connection: { + host: process.env.DB_HOST || "127.0.0.1", + port: process.env.DB_PORT || 3306, + user: process.env.DB_USER || "root", + password: process.env.DB_PASSWORD || "my-secret-pw", + database: process.env.DB_NAME || "hyf_node_week2", + }, +}); + +export default knexInstance; +``` + +At this point verify that your project structure looks like this: + +- api + - snippets.js +- app.js +- database.js +- package.json diff --git a/courses/backend/node/week1/exercises/04-post-endpoint.md b/courses/backend/node/week1/exercises/04-post-endpoint.md new file mode 100644 index 00000000..8da1929e --- /dev/null +++ b/courses/backend/node/week1/exercises/04-post-endpoint.md @@ -0,0 +1,91 @@ +# API + +## `POST /api/snippets` + +Let's start with a simplified version of the `POST /api/snippets` route. First we add the POST route to `api/snippets.js`: + +```js +// Contents of api/snippets.js + +import express from "express"; +import knexInstance from "../database.js"; + +const router = express.Router(); + +// GET /api/snippets +router.get("/", async (request, response) => { + // TODO +}); + +// POST /api/snippets +router.post("/", async (request, response) => { + // TODO +}); + +// TODO: GET /api/snippets/:id + +export default router; +``` + +--- + +To be able to insert a row into the `snippets` table, we need to have data in the `users` table. Create a user and note what the user ID is. + +The POST request we want to make will look something like this: + +```text +POST /api/snippets +{ + "title": "Snippet title", + "contents": "#hello", + "is_private": false +} +``` + +But first, for Express to handle JSON requests, we need to add `app.use(express.json())` to `app.js`: + +```js +// Contents of app.js + +import express from "express"; +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.json()); + +app.get("/", (req, res) => { + res.send("Hello Class!"); +}); + +// Rest of the file... +``` + +--- + +One remaining thing in the setup is to actually use the router we're exporting from `api/snippets.js`. +Inside `app.js`, below `app.use(express.json())`, add the following: + +```js +import snippetsRouter from "./api/snippets.js"; +app.use("/api/snippets", snippetsRouter); +``` + +--- + +**Task:** when we now make a request like + +```text +POST /api/snippets +{ + "title": "Snippet title", + "contents": "#hello", + "is_private": false +} +``` + +you should insert a new row into the `snippets` table with the data from the request body. + +Hints: + +- [Insert with Knex](https://knexjs.org/guide/query-builder.html#insert) +- When creating a snippet we also need to specify a `user_id`. For now, you can just pass in the `user_id` in the request body (alongside the other snippet data) diff --git a/courses/backend/node/week1/exercises/05-get-endpoints.md b/courses/backend/node/week1/exercises/05-get-endpoints.md new file mode 100644 index 00000000..abfdd0ea --- /dev/null +++ b/courses/backend/node/week1/exercises/05-get-endpoints.md @@ -0,0 +1,43 @@ +# API + +## `GET /api/snippets` + +Return all non-private snippets together with some information about the user. Example response: + +```json +[ + { + "id": 1, + "created_at": "...", + "title": "avg.js", + "contents": "console.log('1')", + "is_private": false, + "user": { + "id": 1, + "first_name": "Orhan", + "last_name": "Toy" + } + } +] +``` + +## `GET /api/snippets/:id` + +Return a single snippet. Example response: + +```json +{ + "id": 1, + "created_at": "...", + "title": "avg.js", + "contents": "console.log('1')", + "is_private": true, + "user": { + "id": 1, + "first_name": "Orhan", + "last_name": "Toy" + } +} +``` + +**Not found:** we respond with a 404 if the snippet with the given ID cannot be found. diff --git a/courses/backend/node/week1/exercises/06-auth.md b/courses/backend/node/week1/exercises/06-auth.md new file mode 100644 index 00000000..57ff25e8 --- /dev/null +++ b/courses/backend/node/week1/exercises/06-auth.md @@ -0,0 +1,119 @@ +# API + +Now we can move onto adding authentication for the API. You will have to modify the routes you already created in `api/snippets.js`. + +## Authentication + +The token is used to authenticate certain requests by setting the `Authorization` header, like so: + +`Authorization: token xyz` + +The "xyz" part is the token value that you can look up and find in the `users.token` column. + +## Routes + +See below for details about authentication and the response expectations. + +### `POST /api/snippets` + +**Authenticated access:** if the user has provided a valid token, we go ahead and insert a new row into the `snippets` table. + +Ideally you should try to validate the request and make sure it has + +- a non-empty `title` +- a non-empty `contents` +- `is_private` is optional + +**Invalid authentication:** if the user has provided an invalid token or no token at all, we respond with a 403 Forbidden: + +```json +{ + "error": "Not authorized" +} +``` + +### `GET /api/snippets` + +**Anonymous access:** if there is no `Authorization` header, this should return all non-private snippets together with some information about the user. Example response: + +```json +[ + { + "id": 1, + "created_at": "...", + "title": "avg.js", + "contents": "console.log('1')", + "is_private": false, + "user": { + "id": 1, + "first_name": "Orhan", + "last_name": "Toy" + } + } +] +``` + +**Authenticated access:** if the user has provided a valid token, we return _all_ snippets belonging to that user. Example response: + +```json +[ + { + "id": 1, + "created_at": "...", + "title": "avg.js", + "contents": "console.log('1')", + "is_private": true, + "user": { + "id": 1, + "first_name": "Orhan", + "last_name": "Toy" + } + } +] +``` + +**Invalid authentication:** if the user has provided an invalid token, we respond with a 403 Forbidden response: + +```json +{ + "error": "Not authorized" +} +``` + +### `GET /api/snippets/:id` + +This endpoint should just return the snippet with the given ID: + +```json +{ + "id": 1, + "created_at": "...", + "title": "avg.js", + "contents": "console.log('1')", + "is_private": true, + "user": { + "id": 1, + "first_name": "Orhan", + "last_name": "Toy" + } +} +``` + +**Invalid authentication:** if the user has provided an invalid token, we respond with a 403 Forbidden response: + +```json +{ + "error": "Not authorized" +} +``` + +**Not found:** we respond with a 404 if the snippet with the given ID cannot be found. + +--- + +It is left as an optional exercise to add the following routes: + +- `PUT /api/snippets/:id` to update a snippet +- `DELETE /api/snippets/:id` to delete a snippet + +Also, it could be a good idea to deny the request if the user making the request is not confirmed diff --git a/courses/backend/node/week1/preparation.md b/courses/backend/node/week1/preparation.md new file mode 100644 index 00000000..b5c1ec4f --- /dev/null +++ b/courses/backend/node/week1/preparation.md @@ -0,0 +1,21 @@ +# Preparation + +- (10 min) +- [Introduction to Express](https://youtu.be/9TSBKO59u0Y)> (8 min) +- [Express Route Params](https://youtu.be/MuMs1pLuT7I) (5 min) + +## Flipped classroom videos + +- [Nodejs express part 1 - Class 2](https://youtu.be/4HIq70RzDTY) +- [Nodejs express part 2 - Class 2](https://youtu.be/-J1pd4LgjUo) +- [Nodejs express query parameters and params - Class 2](https://youtu.be/_H-bP10Fmaw) +- [Nodejs express middleware - Class 2](https://youtu.be/ZcwmyYGzBnk) +- [Working with Postman - Class 2](https://youtu.be/zNeOUJPxw2s) + + +### Optional Resources +For more research, you can explore the following resources: + +- [Express JS Crash Course](https://www.youtube.com/watch?v=L72fhGm1tfE) +- [Going out to eat and understanding the basics of Express.js](https://medium.freecodecamp.org/going-out-to-eat-and-understanding-the-basics-of-express-js-f034a029fb66) +- [Express documentation](https://expressjs.com/en/4x/api.html) diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md new file mode 100644 index 00000000..95cd316b --- /dev/null +++ b/courses/backend/node/week1/session-plan.md @@ -0,0 +1,63 @@ +# Session plan + +- Focus on having lots of in class exercises. +- DON'T teach everything, let the students investigate topics on their own as well! +- Focus on how to read documentation, google answers and google errors!! +- Teach towards the students being able to solve the homework. + +## Session Outline + +- Express + - What is express + - Routing (focus on `get` requests) + - `app.use` + - `app.get` + - [Live coding](#app-get-vs-app-use) + - Params `users/:id` + - Query parameters `users?limit=5` + - [Live coding](#query-parameters-vs-parameters) + - Exercise + - Route order + - [Live coding](#route-order) + - Logging and debugging + - Focus on the students understanding **the order** in which things are executed + - Middleware + - `next` method + - Modifying `request` and `response` + - + - [Live coding](#middleware) + - Exercise +- Postman + - Create collection and save queries + - Sending get requests requests + - Query parameters + - Parameters + +## Exercises + +1. [Server](./exercises/01-server.md): Setup project +1. [DB schema](./exercises/02-schema.md): Setup MySQL database schema +1. [API](./exercises/03-api.md): Snippets API exercises + - [POST endpoint](./exercises/04-post-endpoint.md) + - [GET endpoints](./exercises/05-get-endpoints.md) + - [Authentication](./exercises/06-auth.md) + +## Code inspiration + +### app get vs app use + +TODO - What to do with live coding exercises? #122 + +Go to the `teacher-live-coding` [repo](https://github.com/HackYourFuture-CPH/teacher-live-coding), run `npm install` and run using `nodemon ./src/backend/app-use-vs-app-get.js`. Try and implement this functionality from the bottom while explaining. + +### Query parameters vs parameters + +Run `nodemon ./src/backend/query-parameters-vs-parameters.js`. Try and implement this functionality from the bottom while explaining. + +### Route order + +Run `nodemon ./src/backend/route-order.js`. Try and implement this functionality from the bottom while explaining. + +### Middleware + +Run using `nodemon ./src/backend/middleware.js`. Try and implement this functionality from the bottom while explaining. From cef78252542e9ac46882fbc0a40ba4a240a04609 Mon Sep 17 00:00:00 2001 From: Marco Richetta Date: Fri, 22 Aug 2025 19:28:52 +0200 Subject: [PATCH 02/21] follow new modules naming --- .../{exercises => session-materials}/01-server.md | 0 .../{exercises => session-materials}/02-schema.md | 0 .../week1/{exercises => session-materials}/03-api.md | 0 .../04-post-endpoint.md | 0 .../05-get-endpoints.md | 0 .../{exercises => session-materials}/06-auth.md | 0 courses/backend/node/week1/session-plan.md | 12 ++++++------ 7 files changed, 6 insertions(+), 6 deletions(-) rename courses/backend/node/week1/{exercises => session-materials}/01-server.md (100%) rename courses/backend/node/week1/{exercises => session-materials}/02-schema.md (100%) rename courses/backend/node/week1/{exercises => session-materials}/03-api.md (100%) rename courses/backend/node/week1/{exercises => session-materials}/04-post-endpoint.md (100%) rename courses/backend/node/week1/{exercises => session-materials}/05-get-endpoints.md (100%) rename courses/backend/node/week1/{exercises => session-materials}/06-auth.md (100%) diff --git a/courses/backend/node/week1/exercises/01-server.md b/courses/backend/node/week1/session-materials/01-server.md similarity index 100% rename from courses/backend/node/week1/exercises/01-server.md rename to courses/backend/node/week1/session-materials/01-server.md diff --git a/courses/backend/node/week1/exercises/02-schema.md b/courses/backend/node/week1/session-materials/02-schema.md similarity index 100% rename from courses/backend/node/week1/exercises/02-schema.md rename to courses/backend/node/week1/session-materials/02-schema.md diff --git a/courses/backend/node/week1/exercises/03-api.md b/courses/backend/node/week1/session-materials/03-api.md similarity index 100% rename from courses/backend/node/week1/exercises/03-api.md rename to courses/backend/node/week1/session-materials/03-api.md diff --git a/courses/backend/node/week1/exercises/04-post-endpoint.md b/courses/backend/node/week1/session-materials/04-post-endpoint.md similarity index 100% rename from courses/backend/node/week1/exercises/04-post-endpoint.md rename to courses/backend/node/week1/session-materials/04-post-endpoint.md diff --git a/courses/backend/node/week1/exercises/05-get-endpoints.md b/courses/backend/node/week1/session-materials/05-get-endpoints.md similarity index 100% rename from courses/backend/node/week1/exercises/05-get-endpoints.md rename to courses/backend/node/week1/session-materials/05-get-endpoints.md diff --git a/courses/backend/node/week1/exercises/06-auth.md b/courses/backend/node/week1/session-materials/06-auth.md similarity index 100% rename from courses/backend/node/week1/exercises/06-auth.md rename to courses/backend/node/week1/session-materials/06-auth.md diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index 95cd316b..5703877a 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -35,12 +35,12 @@ ## Exercises -1. [Server](./exercises/01-server.md): Setup project -1. [DB schema](./exercises/02-schema.md): Setup MySQL database schema -1. [API](./exercises/03-api.md): Snippets API exercises - - [POST endpoint](./exercises/04-post-endpoint.md) - - [GET endpoints](./exercises/05-get-endpoints.md) - - [Authentication](./exercises/06-auth.md) +1. [Server](./session-materials/01-server.md): Setup project +1. [DB schema](./session-materials/02-schema.md): Setup MySQL database schema +1. [API](./session-materials/03-api.md): Snippets API exercises + - [POST endpoint](./session-materials/04-post-endpoint.md) + - [GET endpoints](./session-materials/05-get-endpoints.md) + - [Authentication](./session-materials/06-auth.md) ## Code inspiration From 94e6b26be272cbfaa3e8ffd0707060059cbb38fc Mon Sep 17 00:00:00 2001 From: Marco Richetta Date: Fri, 22 Aug 2025 19:30:20 +0200 Subject: [PATCH 03/21] move legacy nodejs week3 to backend nodejs week2 --- courses/backend/node/week2/README.md | 46 +++++ courses/backend/node/week2/assignment.md | 211 +++++++++++++++++++++ courses/backend/node/week2/preparation.md | 11 ++ courses/backend/node/week2/session-plan.md | 60 ++++++ 4 files changed, 328 insertions(+) create mode 100644 courses/backend/node/week2/README.md create mode 100644 courses/backend/node/week2/assignment.md create mode 100644 courses/backend/node/week2/preparation.md create mode 100644 courses/backend/node/week2/session-plan.md diff --git a/courses/backend/node/week2/README.md b/courses/backend/node/week2/README.md new file mode 100644 index 00000000..0d67f92b --- /dev/null +++ b/courses/backend/node/week2/README.md @@ -0,0 +1,46 @@ +# Session plan (Week 2) + +In this session we will focus on connecting to a database, building an API, and using Postman to test our API endpoints. We will also cover how to structure our code for better maintainability and scalability. + +## Contents + +- [Preparation](./preparation.md) +- [Session Plan](./session-plan.md) (for mentors) +- [Assignment](./assignment.md) + +## Session Learning goals + +By the end of this session, you will be able to: +TODO - Format as `verb` + +- [ ] Database interaction + - [ ] Connecting to mysql using Knex + - [ ] Environment variables + - [ ] Executing queries using knex +- [ ] API + - [ ] REST + - [ ] CRUD + - [ ] Router verb `GET`, `POST`, `DELETE`, `PUT` + - [ ] POST mention express.json middleware + - [ ] Postman + +TODO - Move this content somewhere else + +### 1. What is Representational State Transfer (REST)? + +Building software is like building houses: architecture is everything. The design of each part is just as important as the utility of it. REST is a specific architectural style for web applications. It serves to organise code in **predictable** ways. + +The most important features of REST are: + +- An application has a `frontend` (client) and a `backend` (server). This is called [separation of concerns](https://medium.com/machine-words/separation-of-concerns-1d735b703a60): each section has its specific job to do. The frontend deals with presenting data in a user friendly way, the backend deals with all the logic and data manipulation +- The server is `stateless`, which means that it doesn't store any data about a client session. Whenever a client sends a request to the server, each request from the client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. This makes it possible to handle requests from millions of users. +- Server responses can be temporarily stored on the client (a browser) using a process called `caching`: storing files like images or webpages in the browser to load the next time you enter a website (instead of getting them from the server, which generally takes longer to do). +- Client-server communication is done through `Hypertext Transfer Protocol (HTTP)` (more on that later), which serves as the style (the how) of communication. + +It's important to know about REST because it teaches us how web applications are designed and holds us to a standard that makes development and usage predictable. However, don't worry if you don't know what any of this means just yet. It's good to be exposed to it, and understanding will come with experience. + +For more research, check the following resource: + +- [What is REST: a simple explanation for beginners](https://medium.com/extend/what-is-rest-a-simple-explanation-for-beginners-part-1-introduction-b4a072f8740f) + +- [@NoerGitKat (lots of web app clones/examples to learn from)](https://github.com/NoerGitKat) diff --git a/courses/backend/node/week2/assignment.md b/courses/backend/node/week2/assignment.md new file mode 100644 index 00000000..2e67f267 --- /dev/null +++ b/courses/backend/node/week2/assignment.md @@ -0,0 +1,211 @@ +# Assignment + +Once again, you will deliver 2 pull requests: + +- A pull request for the **Warmup** - in your regular hyf-homework repository +- A pull request for the additional **meal sharing endpoints** - in the meal-sharing repository + +In both repositories, create a `nodejs-week2` branch from `main` to work on the homework (`git checkout -b nodejs-week2` ). + +## Warmup + +For the warmup you will be handed a Contacts API with a single endpoint: + +- `GET /api/contacts` + +This endpoint accepts a query parameter `sort`. Here's how you can use it: + +- `GET /api/contacts?sort=first_name%20ASC` + - Sorts contacts by first name, ascending +- `GET /api/contacts?sort=last_name%20DESC` + - Sorts contacts by last name, descending + +But this `sort` query parameter has been introduced with a SQL injection vulnerability and the goal is to demonstrate the issue and then fix and remove the vulnerability. + +### Setup + +TODO - Review assignment to work with sqlite. + +Go to `nodejs/week2` in your `hyf-homework` repo: + +```shell +npm init -y +npm i express mysql2 knex +npm i --save-dev nodemon +npm set-script dev "nodemon app.js" +``` + +Make sure you have `"type": "module"` in your `package.json`. + +You should also ensure that the `node_modules/` folder is ignored by Git: + +```shell +echo node_modules/ >> .gitignore +``` + +Create a database/schema called `hyf_node_week2_warmup` with a `contacts` table: + +```sql +CREATE TABLE `contacts` ( + `id` int unsigned NOT NULL AUTO_INCREMENT, + `first_name` varchar(255) NOT NULL, + `last_name` varchar(255) NOT NULL, + `email` varchar(255) DEFAULT NULL, + `phone` varchar(255) DEFAULT NULL, + PRIMARY KEY (`id`) +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; + +-- Sample data +insert into contacts (id, first_name, last_name, email, phone) values (1, 'Selig', 'Matussov', 'smatussov0@pinterest.com', '176-630-4577'); +insert into contacts (id, first_name, last_name, email, phone) values (2, 'Kenny', 'Yerrington', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (3, 'Emilie', 'Gaitskell', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (4, 'Jordon', 'Tokell', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (5, 'Sallyann', 'Persse', 'spersse4@webnode.com', '219-157-2368'); +insert into contacts (id, first_name, last_name, email, phone) values (6, 'Berri', 'Bulter', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (7, 'Lanni', 'Ivanilov', 'livanilov6@fda.gov', null); +insert into contacts (id, first_name, last_name, email, phone) values (8, 'Dagny', 'Milnthorpe', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (9, 'Annadiane', 'Bansal', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (10, 'Tawsha', 'Hackley', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (11, 'Rubetta', 'Ozelton', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (12, 'Charles', 'Boughey', 'cbougheyb@senate.gov', '605-358-5664'); +insert into contacts (id, first_name, last_name, email, phone) values (13, 'Shantee', 'Robbe', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (14, 'Gleda', 'Peat', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (15, 'Arlinda', 'Ethersey', 'aetherseye@biglobe.ne.jp', '916-139-1300'); +insert into contacts (id, first_name, last_name, email, phone) values (16, 'Armando', 'Meachem', 'ameachemf@oaic.gov.au', '631-442-5339'); +insert into contacts (id, first_name, last_name, email, phone) values (17, 'Codi', 'Redhouse', null, '401-953-6897'); +insert into contacts (id, first_name, last_name, email, phone) values (18, 'Ann', 'Buncombe', 'abuncombeh@ow.ly', '210-338-0748'); +insert into contacts (id, first_name, last_name, email, phone) values (19, 'Louis', 'Matzkaitis', 'lmatzkaitisi@ebay.com', '583-996-6979'); +insert into contacts (id, first_name, last_name, email, phone) values (20, 'Jessey', 'Pala', null, null); +insert into contacts (id, first_name, last_name, email, phone) values (21, 'Archy', 'Scipsey', 'ascipseyk@ask.com', '420-983-2426'); +insert into contacts (id, first_name, last_name, email, phone) values (22, 'Benoit', 'Mould', 'bmouldl@bing.com', '271-217-9218'); +insert into contacts (id, first_name, last_name, email, phone) values (23, 'Sherm', 'Girardey', 'sgirardeym@guardian.co.uk', '916-999-2957'); +insert into contacts (id, first_name, last_name, email, phone) values (24, 'Raquel', 'Mudge', 'rmudgen@slate.com', '789-830-7473'); +insert into contacts (id, first_name, last_name, email, phone) values (25, 'Tabor', 'Reavey', null, null); +``` + +Create `app.js`: + +```js +import knex from "knex"; +const knexInstance = knex({ + client: "mysql2", + connection: { + host: process.env.DB_HOST || "127.0.0.1", + port: process.env.DB_PORT || 3306, + user: process.env.DB_USER || "root", + password: process.env.DB_PASSWORD || "my-secret-pw", + database: process.env.DB_NAME || "hyf_node_week2_warmup", + multipleStatements: true, + }, +}); + +import express from "express"; +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.json()); + +const apiRouter = express.Router(); +app.use("/api", apiRouter); + +const contactsAPIRouter = express.Router(); +apiRouter.use("/contacts", contactsAPIRouter); + +contactsAPIRouter.get("/", async (req, res) => { + let query = knexInstance.select("*").from("contacts"); + + if ("sort" in req.query) { + const orderBy = req.query.sort.toString(); + if (orderBy.length > 0) { + query = query.orderByRaw(orderBy); + } + } + + console.log("SQL", query.toSQL().sql); + + try { + const data = await query; + res.json({ data }); + } catch (e) { + console.error(e); + res.status(500).json({ error: "Internal server error" }); + } +}); + +app.listen(port, () => { + console.log(`Listening on port ${port}`); +}); +``` + +As mentioned above, the `sort` query parameter has been introduced with a SQL injection vulnerability. + +First, you should demonstrate the SQL injection and that it for instance is possible to drop/delete the `contacts` table with the `sort` query parameter. +You can for instance demonstrate this with a screen recording and include it in the PR description. + +After having demonstrated the SQL injection vulnerability, the goal is then to fix the issue by updating `app.js`. + +**Hint:** the `multipleStatements: true` part in the configuration indicates how you can use the vulnerability. The configuration should not be changed though, the SQL injection should be fixed by making changes in the `/api/contacts` route. + +## Meal sharing endpoints + +You will continue working in the meal-sharing repository for this task. + +You should have the basic [CRUD](https://www.freecodecamp.org/news/crud-operations-explained/) endpoints for **meals** and **reservations** as the result of last week's homework. This week, you will add **query parameters**, that will allow you to **sort** and **filter** the information retrieved from the database. + +### Routes + +#### Meals + +Work with your `GET api/meals` route to add the query parameters. +Make sure that the query parameters can be combined, f.x. `?limit=4&maxPrice=90`. + +| Parameter | Data type | Description | Example | +| ----------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | +| `maxPrice` | Number | Returns all meals that are cheaper than `maxPrice`. | `api/meals?maxPrice=90` | +| `availableReservations` | Boolean | Returns all meals that still have available spots left, if `true`. If `false`, return meals that have no available spots left.[^1] | `api/meals?availableReservations=true` | +| `title` | String | Returns all meals that partially match the given title. `Rød grød` will match the meal with the title `Rød grød med fløde`. | `api/meals?title=Indian%20platter` | +| `dateAfter` | Date | Returns all meals where the date for `when` is after the given date. | `api/meals?dateAfter=2022-10-01` | +| `dateBefore` | Date | Returns all meals where the date for `when` is before the given date. | `api/meals?dateBefore=2022-08-08` | +| `limit` | Number | Returns the given number of meals. | `api/meals?limit=7` | +| `sortKey`[^2] | String | Returns all meals sorted by the given key. Allows `when`, `max_reservations` and `price` as keys. Default sorting order is asc(ending). | `api/meals?sortKey=price` | +| `sortDir`[^3] | String | Returns all meals sorted in the given direction. Only works combined with the `sortKey` and allows `asc` or `desc`. | `api/meals?sortKey=price&sortDir=desc` | + +[^1]: `availableReservations` requires you to work with several database tables at once. Try practicing the right query in MySQL Workbench first (you might have it from Database week2 homework) and once you have it working, build it with `knex`. + +[^2]: This used to be `sort_key` in a previous version of the homework text. + +[^3]: This used to be `sort_dir` in a previous version of the homework text. + +#### Reviews + +By now, you have the basic set of endpoints for **meals** and **reservations** and even a collection of query parameters for **meals**. To practice a bit more and finalize the basic backend functionality, create the set of routes for **reviews**: + +| Route | HTTP method | Description | +| ----------------------------- | ----------- | ---------------------------------------- | +| `/api/reviews` | GET | Returns all reviews. | +| `/api/meals/:meal_id/reviews` | GET | Returns all reviews for a specific meal. | +| `/api/reviews` | POST | Adds a new review to the database. | +| `/api/reviews/:id` | GET | Returns a review by `id`. | +| `/api/reviews/:id` | PUT | Updates the review by `id`. | +| `/api/reviews/:id` | DELETE | Deletes the review by `id`. | + +#### Knex + +You should try to avoid using `knex.raw` and instead use the different `knex` functions, for example: + +- `.select`, `.from`, `.where`, `join`, `leftJoin` +- `.insert` +- `.update` +- `.del` (for deletion) + +Check out the [Knex cheatsheet](https://devhints.io/knex)! + +## Hand in homework + +Need to brush up on the homework hand-in process? + +Check [this resource](https://github.com/HackYourFuture-CPH/Git/blob/main/homework-submission.md) to remember how to hand in the homework correctly! + +## Feedback + +And finally, please take two minutes to answer the survey [here](https://forms.gle/YG5KCnSCPhb8dJAL9) to give feedback to the staff and mentors. diff --git a/courses/backend/node/week2/preparation.md b/courses/backend/node/week2/preparation.md new file mode 100644 index 00000000..0a2c6f55 --- /dev/null +++ b/courses/backend/node/week2/preparation.md @@ -0,0 +1,11 @@ +# Preparation + +- [NodeJS Web API with KNEX and Express](https://www.youtube.com/watch?v=QNw9q4YXR4E) (15 min) +- up until the `The Visual Studio Code REST client` section (15 min) +- - Free API for testing and prototyping. (5 min) + +## Flipped classroom videos + +- [Connecting nodejs to a database using Knex part 1 - Class 3](https://youtu.be/W5xFbiAl4bo) +- [Connecting nodejs to a database using Knex part 2 - Class 3](https://youtu.be/cacTSGU7Hrc) +- [Creating an api using nodejs and express - Class 3](https://youtu.be/i-BUdUMz6Zk) diff --git a/courses/backend/node/week2/session-plan.md b/courses/backend/node/week2/session-plan.md new file mode 100644 index 00000000..972104b6 --- /dev/null +++ b/courses/backend/node/week2/session-plan.md @@ -0,0 +1,60 @@ +# Lesson plan + +- Focus on having lots of in class exercises. +- DON'T teach everything, let the students investigate topics on their own as well! +- Focus on how to read documentation, google answers and google errors!! +- Teach towards the students being able to solve the homework. + +Remember to add the code you wrote in the class to the relevant class branch's class work folder. If the branch has not been created just create and push it :) If you don't have access, write to one from the core team. You can see an example below! + +To find examples of what teachers have taught before go to the class branches in the classwork folder, e.g. [class 07](https://github.com/HackYourFuture-CPH/JavaScript/tree/class07/JavaScript1/Week1/classwork) + +If you find anything that could be improved then please create a pull request! We welcome changes, so please get involved if you have any ideas!!! + +--- + +- Database interaction + - Connecting to mysql using knex + - Executing queries + - `select`, `create`. You could let the students figure out how `delete` and `update` works + - [Code inspiration](#phonebook-database) especially focus on the promise and query part +- API + - REST + - CRUD + - Router verb `GET`, `POST`, `DELETE`, `PUT` + - Especially focus on post with `app.use(express.urlencoded({ extended: true }));` and `app.use(express.json());` + - [Code inspiration](#phonebook-api) +- Postman + - `POST`, `DELETE`, `PUT` requests +- Exercise finish concerts api + +## Flipped classroom videos + +[Flipped classroom videos](https://github.com/HackYourFuture-CPH/node.js/blob/main/week1/preparation.md#flipped-classroom-videos) + +## Code inspiration + +### Phonebook database + +- Go to the `teacher-live-coding` [repo](https://github.com/HackYourFuture-CPH/teacher-live-coding), to the relevant folder +- Copy the `.env.example` and rename the copied file to `.env` +- Run `npm install` +- Start the application by running `nodemon ./src/backend/phonebook-database-queries.js` + +Try and implement this functionality from the bottom while explaining. + +### Phonebook api + +Start the application by running `nodemon ./src/backend/create-an-api.js`. + +The following two routes have been created, get help by the students to create some of the other routes. + +| Url | Verb | Functionality | Example | +| ------------------- | ------ | --------------------------- | -------------------- | +| `api/contacts/` | GET | Returns all contacts | `GET api/contacts/` | +| `api/contacts/` | POST | Adds a new contact | `POST api/contacts/` | +| `api/contacts/{id}` | GET | Returns contact by `id` | `GET api/contacts/2` | +| `api/contacts/{id}` | PUT | Updates the contact by `id` | `PUT api/contacts/2` | +| `api/contacts/{id}` | DELETE | Deletes the contact by `id` | `DELETE contacts/2` | + +Thank you very much for teaching NodeJS. Please don't hesitate to give feedback by clicking [here](https://forms.gle/sAuVhsTmJ1qSmjgJ6) (teachers and teacher assistants). For homework reviewers, please access the survey [here](https://forms.gle/nVbX9ShusF2a5Aa87). From 47351d0451f478dedcaffb6fded7b032970127e6 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Tue, 9 Sep 2025 20:57:58 +0200 Subject: [PATCH 04/21] chore: refreshed links removed stale code --- courses/backend/node/week1/README.md | 3 +-- courses/backend/node/week1/assignment.md | 7 +++---- courses/backend/node/week1/preparation.md | 10 +--------- .../node/week1/session-materials/01-server.md | 5 ++--- .../node/week1/session-materials/03-api.md | 12 +++++------- .../session-materials/04-post-endpoint.md | 2 +- courses/backend/node/week2/assignment.md | 19 ++++++++----------- courses/backend/node/week2/preparation.md | 6 ------ courses/backend/node/week2/session-plan.md | 10 ++-------- 9 files changed, 23 insertions(+), 51 deletions(-) diff --git a/courses/backend/node/week1/README.md b/courses/backend/node/week1/README.md index e4637f43..8978e456 100644 --- a/courses/backend/node/week1/README.md +++ b/courses/backend/node/week1/README.md @@ -18,7 +18,6 @@ By the end of this session, you will be able to: - [ ] Apply middleware functions in Express to process requests and responses. - [ ] Use Postman to test and debug APIs you have built. - TODO - Move this content somewhere else When writing an Express application we are registering routes on HTTP keywords (GET,POST,PUT, DELETE) and handler functions for those routes. @@ -70,7 +69,7 @@ There are a few good extensions or middleware that is easy to plug into express ### Express.js -In Node.js it's possible to make a HTTP server using the native `http` module. However, this is rarely used in practice. Instead, we'll use [Express.js](https://expressjs.com/en/4x/api.html), a backend framework that can do what the `http` module does and much more (in a simpler, faster and more readable way). +In Node.js it's possible to make a HTTP server using the native `http` module. However, this is rarely used in practice. Instead, we'll use [Express.js](https://expressjs.com/), a backend framework that can do what the `http` module does and much more (in a simpler, faster and more readable way). Practically speaking, what can we do with a web server like `http` or `Express`? All the magic that makes the frontend work: diff --git a/courses/backend/node/week1/assignment.md b/courses/backend/node/week1/assignment.md index 8c5a0a74..7241edb5 100644 --- a/courses/backend/node/week1/assignment.md +++ b/courses/backend/node/week1/assignment.md @@ -41,8 +41,7 @@ Go to `nodejs/week1` in your `hyf-homework` repo: npm init -y npm pkg set type="module" npm i express -npm i --save-dev nodemon -npm pkg set scripts.dev="nodemon app.js" +npm pkg set scripts.dev="node --watch app.js" ``` You should ensure that the `node_modules/` folder is ignored by Git: @@ -131,7 +130,7 @@ If both `q` (query parameter) and `fields` (in body) are provided, we should res ## Meal sharing endpoints -You will continue working in the meal-sharing repository for this homework. This week you will build more endpoints, developing some [CRUD](https://www.freecodecamp.org/news/crud-operations-explained/) operations for your future meal sharing website backend functionality. +You will continue working in the meal-sharing repository for this homework. This week you will build more endpoints, developing some [CRUD operations](https://www.freecodecamp.org/news/crud-operations-explained/) for your future meal sharing website backend functionality. ### Routes @@ -143,7 +142,7 @@ For this week's homework, we will add two categories of routes: meals and reserv - The reservation routes will live in `/api/src/routers/reservations.js` This means that we will end up having two Routers: a meals router and a reservations router. -You can read more about Express Routers [here](https://expressjs.com/en/4x/api.html#router). +You can [read more about Express Routers](https://expressjs.com/en/guide/routing.html). You can reference the file `/api/src/routers/nested.js` for an example, and see how it is used in `/api/src/index.js`. diff --git a/courses/backend/node/week1/preparation.md b/courses/backend/node/week1/preparation.md index b5c1ec4f..5d913023 100644 --- a/courses/backend/node/week1/preparation.md +++ b/courses/backend/node/week1/preparation.md @@ -4,16 +4,8 @@ - [Introduction to Express](https://youtu.be/9TSBKO59u0Y)> (8 min) - [Express Route Params](https://youtu.be/MuMs1pLuT7I) (5 min) -## Flipped classroom videos +## Optional Resources -- [Nodejs express part 1 - Class 2](https://youtu.be/4HIq70RzDTY) -- [Nodejs express part 2 - Class 2](https://youtu.be/-J1pd4LgjUo) -- [Nodejs express query parameters and params - Class 2](https://youtu.be/_H-bP10Fmaw) -- [Nodejs express middleware - Class 2](https://youtu.be/ZcwmyYGzBnk) -- [Working with Postman - Class 2](https://youtu.be/zNeOUJPxw2s) - - -### Optional Resources For more research, you can explore the following resources: - [Express JS Crash Course](https://www.youtube.com/watch?v=L72fhGm1tfE) diff --git a/courses/backend/node/week1/session-materials/01-server.md b/courses/backend/node/week1/session-materials/01-server.md index 8469bc89..af00580d 100644 --- a/courses/backend/node/week1/session-materials/01-server.md +++ b/courses/backend/node/week1/session-materials/01-server.md @@ -16,7 +16,7 @@ Initialize and install: ```shell npm init -y npm pkg set type="module" -npm install express mysql2 knex +npm install express echo node_modules/ >> .gitignore ``` @@ -39,8 +39,7 @@ app.listen(port, () => { --- ```shell -npm install --save-dev nodemon -npm pkg set scripts.dev="nodemon app.js" +npm pkg set scripts.dev="node --watch app.js" npm run dev ``` diff --git a/courses/backend/node/week1/session-materials/03-api.md b/courses/backend/node/week1/session-materials/03-api.md index ec3c3cf0..f34b851f 100644 --- a/courses/backend/node/week1/session-materials/03-api.md +++ b/courses/backend/node/week1/session-materials/03-api.md @@ -6,7 +6,7 @@ The end goal of the exercise is to implement the following routes: - `GET /api/snippets` to get a list of snippets - `GET /api/snippets/:id` to get a single snippet -We will create the snippet routes in a different file, `api/snippets.js`, which will export an [Express Router](https://expressjs.com/en/4x/api.html#router). +We will create the snippet routes in a different file, `api/snippets.js`, which will export an [Express router](https://expressjs.com/en/starter/basic-routing.html). That will look something like this: @@ -34,14 +34,12 @@ We will also have the database connection in a separate file: import knex from "knex"; +const dbFile = "PATH_TO_YOUR_SQLITE_DB"; + const knexInstance = knex({ - client: "mysql2", + client: "sqlite3", connection: { - host: process.env.DB_HOST || "127.0.0.1", - port: process.env.DB_PORT || 3306, - user: process.env.DB_USER || "root", - password: process.env.DB_PASSWORD || "my-secret-pw", - database: process.env.DB_NAME || "hyf_node_week2", + filename: dbFile, }, }); diff --git a/courses/backend/node/week1/session-materials/04-post-endpoint.md b/courses/backend/node/week1/session-materials/04-post-endpoint.md index 8da1929e..2ef68c65 100644 --- a/courses/backend/node/week1/session-materials/04-post-endpoint.md +++ b/courses/backend/node/week1/session-materials/04-post-endpoint.md @@ -8,7 +8,7 @@ Let's start with a simplified version of the `POST /api/snippets` route. First w // Contents of api/snippets.js import express from "express"; -import knexInstance from "../database.js"; +import knex from "../database.js"; const router = express.Router(); diff --git a/courses/backend/node/week2/assignment.md b/courses/backend/node/week2/assignment.md index 2e67f267..c8223d69 100644 --- a/courses/backend/node/week2/assignment.md +++ b/courses/backend/node/week2/assignment.md @@ -30,9 +30,8 @@ Go to `nodejs/week2` in your `hyf-homework` repo: ```shell npm init -y -npm i express mysql2 knex -npm i --save-dev nodemon -npm set-script dev "nodemon app.js" +npm i express sqlite3 knex +npm set-script dev "node --watch app.js" ``` Make sure you have `"type": "module"` in your `package.json`. @@ -87,19 +86,17 @@ Create `app.js`: ```js import knex from "knex"; +import express from "express"; + +const dbFile = "PATH_TO_YOUR_SQLITE_DB"; + const knexInstance = knex({ - client: "mysql2", + client: "sqlite3", connection: { - host: process.env.DB_HOST || "127.0.0.1", - port: process.env.DB_PORT || 3306, - user: process.env.DB_USER || "root", - password: process.env.DB_PASSWORD || "my-secret-pw", - database: process.env.DB_NAME || "hyf_node_week2_warmup", - multipleStatements: true, + filename: dbFile, }, }); -import express from "express"; const app = express(); const port = process.env.PORT || 3000; diff --git a/courses/backend/node/week2/preparation.md b/courses/backend/node/week2/preparation.md index 0a2c6f55..6196cd78 100644 --- a/courses/backend/node/week2/preparation.md +++ b/courses/backend/node/week2/preparation.md @@ -3,9 +3,3 @@ - [NodeJS Web API with KNEX and Express](https://www.youtube.com/watch?v=QNw9q4YXR4E) (15 min) - up until the `The Visual Studio Code REST client` section (15 min) - - Free API for testing and prototyping. (5 min) - -## Flipped classroom videos - -- [Connecting nodejs to a database using Knex part 1 - Class 3](https://youtu.be/W5xFbiAl4bo) -- [Connecting nodejs to a database using Knex part 2 - Class 3](https://youtu.be/cacTSGU7Hrc) -- [Creating an api using nodejs and express - Class 3](https://youtu.be/i-BUdUMz6Zk) diff --git a/courses/backend/node/week2/session-plan.md b/courses/backend/node/week2/session-plan.md index 972104b6..9fef4751 100644 --- a/courses/backend/node/week2/session-plan.md +++ b/courses/backend/node/week2/session-plan.md @@ -7,8 +7,6 @@ Remember to add the code you wrote in the class to the relevant class branch's class work folder. If the branch has not been created just create and push it :) If you don't have access, write to one from the core team. You can see an example below! -To find examples of what teachers have taught before go to the class branches in the classwork folder, e.g. [class 07](https://github.com/HackYourFuture-CPH/JavaScript/tree/class07/JavaScript1/Week1/classwork) - If you find anything that could be improved then please create a pull request! We welcome changes, so please get involved if you have any ideas!!! --- @@ -28,10 +26,6 @@ If you find anything that could be improved then please create a pull request! W - `POST`, `DELETE`, `PUT` requests - Exercise finish concerts api -## Flipped classroom videos - -[Flipped classroom videos](https://github.com/HackYourFuture-CPH/node.js/blob/main/week1/preparation.md#flipped-classroom-videos) - ## Code inspiration ### Phonebook database @@ -39,13 +33,13 @@ If you find anything that could be improved then please create a pull request! W - Go to the `teacher-live-coding` [repo](https://github.com/HackYourFuture-CPH/teacher-live-coding), to the relevant folder - Copy the `.env.example` and rename the copied file to `.env` - Run `npm install` -- Start the application by running `nodemon ./src/backend/phonebook-database-queries.js` +- Start the application by running `node --watch ./src/backend/phonebook-database-queries.js` Try and implement this functionality from the bottom while explaining. ### Phonebook api -Start the application by running `nodemon ./src/backend/create-an-api.js`. +Start the application by running `node --watch ./src/backend/create-an-api.js`. The following two routes have been created, get help by the students to create some of the other routes. From 0fe5acecac4a9ffc816cd36ebde28b34f933207c Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 15 Sep 2025 20:44:03 +0200 Subject: [PATCH 05/21] chore: scaffold teacher materials --- .../node/module-materials/examples/router.js | 8 + .../backend/node/module-materials/index.js | 14 + .../node/module-materials/package-lock.json | 2431 +++++++++++++++++ .../node/module-materials/package.json | 17 + 4 files changed, 2470 insertions(+) create mode 100644 courses/backend/node/module-materials/examples/router.js create mode 100644 courses/backend/node/module-materials/index.js create mode 100644 courses/backend/node/module-materials/package-lock.json create mode 100644 courses/backend/node/module-materials/package.json diff --git a/courses/backend/node/module-materials/examples/router.js b/courses/backend/node/module-materials/examples/router.js new file mode 100644 index 00000000..ed77c983 --- /dev/null +++ b/courses/backend/node/module-materials/examples/router.js @@ -0,0 +1,8 @@ +import express from "express"; +const router = express.Router(); + +router.get("/", async (_request, response) => { + response.send({ data: "from app.use" }); +}); + +export default router; diff --git a/courses/backend/node/module-materials/index.js b/courses/backend/node/module-materials/index.js new file mode 100644 index 00000000..f6505351 --- /dev/null +++ b/courses/backend/node/module-materials/index.js @@ -0,0 +1,14 @@ +import express from "express"; +import router from "./examples/router.js"; + +const app = express(); + +app.get("/", (_req, res) => { + res.send({ data: "from app.get" }); +}); + +app.use("/use", router); + +app.listen(3000, function () { + console.log(`> Ready on http://localhost:3000`); +}); diff --git a/courses/backend/node/module-materials/package-lock.json b/courses/backend/node/module-materials/package-lock.json new file mode 100644 index 00000000..e54eaece --- /dev/null +++ b/courses/backend/node/module-materials/package-lock.json @@ -0,0 +1,2431 @@ +{ + "name": "node-module-materials", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "node-module-materials", + "version": "1.0.0", + "license": "ISC", + "dependencies": { + "express": "^5.1.0", + "knex": "^3.1.0", + "sqlite3": "^5.1.7" + } + }, + "node_modules/@gar/promisify": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz", + "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==", + "license": "MIT", + "optional": true + }, + "node_modules/@npmcli/fs": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz", + "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@gar/promisify": "^1.0.1", + "semver": "^7.3.5" + } + }, + "node_modules/@npmcli/move-file": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz", + "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==", + "deprecated": "This functionality has been moved to @npmcli/fs", + "license": "MIT", + "optional": true, + "dependencies": { + "mkdirp": "^1.0.4", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@tootallnate/once": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz", + "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "license": "ISC", + "optional": true + }, + "node_modules/accepts": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", + "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", + "license": "MIT", + "dependencies": { + "mime-types": "^3.0.0", + "negotiator": "^1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agent-base": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "debug": "4" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/aggregate-error": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz", + "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==", + "license": "MIT", + "optional": true, + "dependencies": { + "clean-stack": "^2.0.0", + "indent-string": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/aproba": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.1.0.tgz", + "integrity": "sha512-tLIEcj5GuR2RSTnxNKdkK0dJ/GrC7P38sUkiDmDuHfsHmbagTFAxDVIBltoklXEVIQ/f14IL8IMJ5pn9Hez1Ew==", + "license": "ISC", + "optional": true + }, + "node_modules/are-we-there-yet": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz", + "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "license": "MIT", + "optional": true + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/body-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.0.tgz", + "integrity": "sha512-02qvAaxv8tp7fBa/mw1ga98OGm+eCbqzJOKoRt70sLmfEEi+jyBYVTDGfCL/k06/4EMk/z01gCe7HoCH/f2LTg==", + "license": "MIT", + "dependencies": { + "bytes": "^3.1.2", + "content-type": "^1.0.5", + "debug": "^4.4.0", + "http-errors": "^2.0.0", + "iconv-lite": "^0.6.3", + "on-finished": "^2.4.1", + "qs": "^6.14.0", + "raw-body": "^3.0.0", + "type-is": "^2.0.0" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "license": "MIT", + "optional": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/cacache": { + "version": "15.3.0", + "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz", + "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "@npmcli/fs": "^1.0.0", + "@npmcli/move-file": "^1.0.1", + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "glob": "^7.1.4", + "infer-owner": "^1.0.4", + "lru-cache": "^6.0.0", + "minipass": "^3.1.1", + "minipass-collect": "^1.0.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.2", + "mkdirp": "^1.0.3", + "p-map": "^4.0.0", + "promise-inflight": "^1.0.1", + "rimraf": "^3.0.2", + "ssri": "^8.0.1", + "tar": "^6.0.2", + "unique-filename": "^1.1.1" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chownr": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==", + "license": "ISC", + "engines": { + "node": ">=10" + } + }, + "node_modules/clean-stack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz", + "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==", + "license": "ISC", + "optional": true, + "bin": { + "color-support": "bin.js" + } + }, + "node_modules/colorette": { + "version": "2.0.19", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.19.tgz", + "integrity": "sha512-3tlv/dIP7FWvj3BsbHrGLJ6l/oKh1O3TcgBqMn+yyCagOxc23fyzDS6HypQbgxWbkpDnf52p1LuR4eWDQ/K9WQ==", + "license": "MIT" + }, + "node_modules/commander": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-10.0.1.tgz", + "integrity": "sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==", + "license": "MIT", + "engines": { + "node": ">=14" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "license": "MIT", + "optional": true + }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==", + "license": "ISC", + "optional": true + }, + "node_modules/content-disposition": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.0.tgz", + "integrity": "sha512-Au9nRL8VNUut/XSzbQA38+M78dzP4D+eqg3gfJHMIHHYa3bg067xj1KxMUWj+VULbiZMowKngFFbKczUrNJ1mg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", + "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", + "license": "MIT", + "engines": { + "node": ">=6.6.0" + } + }, + "node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==", + "license": "MIT", + "optional": true + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/detect-libc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.0.tgz", + "integrity": "sha512-vEtk+OcP7VBRtQZ1EJ3bdgzSfBjgnEalLTp5zjJrS+2Z1w2KZly4SBdac/WDU3hhsNAZ9E8SC96ME4Ey8MZ7cg==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT", + "optional": true + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/encoding": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz", + "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==", + "license": "MIT", + "optional": true, + "dependencies": { + "iconv-lite": "^0.6.2" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/env-paths": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", + "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/err-code": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz", + "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==", + "license": "MIT", + "optional": true + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escalade": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.2.0.tgz", + "integrity": "sha512-WUj2qlxaQtO4g6Pq5c29GTcWGDyd8itL8zTlipgECz3JesAiiOKotd8JU6otB3PACgG6xkJUyVhboMS+bje/jA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/esm": { + "version": "3.2.25", + "resolved": "https://registry.npmjs.org/esm/-/esm-3.2.25.tgz", + "integrity": "sha512-U1suiZ2oDVWv4zPO56S0NcR5QriEahGtdN2OR6FiOG4WJvcjBVFB0qI4+eKoWFH483PKGuLuu6V8Z4T5g63UVA==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/express/-/express-5.1.0.tgz", + "integrity": "sha512-DT9ck5YIRU+8GYzzU5kT3eHGA5iL+1Zd0EutOmTE9Dtk+Tvuzd23VBU+ec7HPNSTxXYO55gPV/hq4pSBJDjFpA==", + "license": "MIT", + "dependencies": { + "accepts": "^2.0.0", + "body-parser": "^2.2.0", + "content-disposition": "^1.0.0", + "content-type": "^1.0.5", + "cookie": "^0.7.1", + "cookie-signature": "^1.2.1", + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "finalhandler": "^2.1.0", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "merge-descriptors": "^2.0.0", + "mime-types": "^3.0.0", + "on-finished": "^2.4.1", + "once": "^1.4.0", + "parseurl": "^1.3.3", + "proxy-addr": "^2.0.7", + "qs": "^6.14.0", + "range-parser": "^1.2.1", + "router": "^2.2.0", + "send": "^1.1.0", + "serve-static": "^2.2.0", + "statuses": "^2.0.1", + "type-is": "^2.0.1", + "vary": "^1.1.2" + }, + "engines": { + "node": ">= 18" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/finalhandler": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.0.tgz", + "integrity": "sha512-/t88Ty3d5JWQbWYgaOGCCYfXRwV1+be02WqYYlL6h0lEiUAMPM8o8qKGO01YIkOHzka2up08wvgYD0mDiI+q3Q==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "on-finished": "^2.4.1", + "parseurl": "^1.3.3", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", + "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "license": "ISC", + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "license": "ISC", + "optional": true + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/gauge": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz", + "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.3", + "console-control-strings": "^1.1.0", + "has-unicode": "^2.0.1", + "signal-exit": "^3.0.7", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.5" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-package-type": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/get-package-type/-/get-package-type-0.1.0.tgz", + "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/getopts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/getopts/-/getopts-2.3.0.tgz", + "integrity": "sha512-5eDf9fuSXwxBL6q5HX+dhDj+dslFGWzU5thZ9kNKUkcPtaPdatmUFKwHFrLb/uf/WpA4BHET+AX3Scl56cAjpA==", + "license": "MIT" + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "deprecated": "Glob versions prior to v9 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/graceful-fs": { + "version": "4.2.11", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz", + "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", + "license": "ISC", + "optional": true + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==", + "license": "ISC", + "optional": true + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/http-cache-semantics": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.2.0.tgz", + "integrity": "sha512-dTxcvPXqPvXBQpq5dUr6mEMJX4oIEFv6bwom3FDwKRDsuIjjJGANqhBuoAn9c1RQJIdAKav33ED65E2ys+87QQ==", + "license": "BSD-2-Clause", + "optional": true + }, + "node_modules/http-errors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz", + "integrity": "sha512-FtwrG/euBzaEjYeRqOgly7G0qviiXoJWnvEH2Z1plBdXgbyjv34pHTSb9zoeHMyDy33+DWy5Wt9Wo+TURtOYSQ==", + "license": "MIT", + "dependencies": { + "depd": "2.0.0", + "inherits": "2.0.4", + "setprototypeof": "1.2.0", + "statuses": "2.0.1", + "toidentifier": "1.0.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-errors/node_modules/statuses": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.1.tgz", + "integrity": "sha512-RwNA9Z/7PrK06rYLIzFMlaF+l73iwpzsqRIFgbMLbTcLD6cOao82TaWefPXQvB2fOC4AjuYSEndS7N/mTCbkdQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/http-proxy-agent": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz", + "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@tootallnate/once": "1", + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/https-proxy-agent": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "6", + "debug": "4" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz", + "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/indent-string": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz", + "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/infer-owner": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz", + "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==", + "license": "ISC", + "optional": true + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", + "license": "ISC", + "optional": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/ip-address": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", + "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 12" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-core-module": { + "version": "2.16.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.16.1.tgz", + "integrity": "sha512-UfoeMA6fIJ8wTYFEUjelnaGI67v6+N7qXJEvQuIGa99l4xsCruSYOVSQ0uPANn4dAzm8lkYPaKLrrijLq7x23w==", + "license": "MIT", + "dependencies": { + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-lambda": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz", + "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==", + "license": "MIT", + "optional": true + }, + "node_modules/is-promise": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", + "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", + "license": "MIT" + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "license": "ISC", + "optional": true + }, + "node_modules/knex": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/knex/-/knex-3.1.0.tgz", + "integrity": "sha512-GLoII6hR0c4ti243gMs5/1Rb3B+AjwMOfjYm97pu0FOQa7JH56hgBxYf5WK2525ceSbBY1cjeZ9yk99GPMB6Kw==", + "license": "MIT", + "dependencies": { + "colorette": "2.0.19", + "commander": "^10.0.0", + "debug": "4.3.4", + "escalade": "^3.1.1", + "esm": "^3.2.25", + "get-package-type": "^0.1.0", + "getopts": "2.3.0", + "interpret": "^2.2.0", + "lodash": "^4.17.21", + "pg-connection-string": "2.6.2", + "rechoir": "^0.8.0", + "resolve-from": "^5.0.0", + "tarn": "^3.0.2", + "tildify": "2.0.0" + }, + "bin": { + "knex": "bin/cli.js" + }, + "engines": { + "node": ">=16" + }, + "peerDependenciesMeta": { + "better-sqlite3": { + "optional": true + }, + "mysql": { + "optional": true + }, + "mysql2": { + "optional": true + }, + "pg": { + "optional": true + }, + "pg-native": { + "optional": true + }, + "sqlite3": { + "optional": true + }, + "tedious": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "license": "MIT", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/knex/node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "license": "MIT" + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" + }, + "node_modules/lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "license": "ISC", + "optional": true, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/make-fetch-happen": { + "version": "9.1.0", + "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz", + "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==", + "license": "ISC", + "optional": true, + "dependencies": { + "agentkeepalive": "^4.1.3", + "cacache": "^15.2.0", + "http-cache-semantics": "^4.1.0", + "http-proxy-agent": "^4.0.1", + "https-proxy-agent": "^5.0.0", + "is-lambda": "^1.0.1", + "lru-cache": "^6.0.0", + "minipass": "^3.1.3", + "minipass-collect": "^1.0.2", + "minipass-fetch": "^1.3.2", + "minipass-flush": "^1.0.5", + "minipass-pipeline": "^1.2.4", + "negotiator": "^0.6.2", + "promise-retry": "^2.0.1", + "socks-proxy-agent": "^6.0.0", + "ssri": "^8.0.0" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/make-fetch-happen/node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", + "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/merge-descriptors": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", + "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.1.tgz", + "integrity": "sha512-xRc4oEhT6eaBpU1XF7AjpOFD+xQmXNB5OVKwp4tqCuBpHLS/ZbBDrc07mYTDqVMg6PfxUjjNp85O6Cd2Z/5HWA==", + "license": "MIT", + "dependencies": { + "mime-db": "^1.54.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "license": "ISC", + "optional": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "license": "ISC", + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-collect": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz", + "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-fetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz", + "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==", + "license": "MIT", + "optional": true, + "dependencies": { + "minipass": "^3.1.0", + "minipass-sized": "^1.0.3", + "minizlib": "^2.0.0" + }, + "engines": { + "node": ">=8" + }, + "optionalDependencies": { + "encoding": "^0.1.12" + } + }, + "node_modules/minipass-flush": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz", + "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/minipass-pipeline": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz", + "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minipass-sized": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz", + "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/minizlib": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", + "license": "MIT", + "dependencies": { + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/mkdirp": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", + "license": "MIT", + "bin": { + "mkdirp": "bin/cmd.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", + "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.77.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.77.0.tgz", + "integrity": "sha512-DSmt0OEcLoK4i3NuscSbGjOf3bqiDEutejqENSplMSFA/gmB8mkED9G4pKWnPl7MDU4rSHebKPHeitpDfyH0cQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-addon-api": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-7.1.1.tgz", + "integrity": "sha512-5m3bsyrjFWE1xf7nz7YXdN4udnVtXK6/Yfgn5qnahL6bCkf2yKt4k3nuTKAtT4r3IG8JNR2ncsIMdZuAzJjHQQ==", + "license": "MIT" + }, + "node_modules/node-gyp": { + "version": "8.4.1", + "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz", + "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==", + "license": "MIT", + "optional": true, + "dependencies": { + "env-paths": "^2.2.0", + "glob": "^7.1.4", + "graceful-fs": "^4.2.6", + "make-fetch-happen": "^9.1.0", + "nopt": "^5.0.0", + "npmlog": "^6.0.0", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.2", + "which": "^2.0.2" + }, + "bin": { + "node-gyp": "bin/node-gyp.js" + }, + "engines": { + "node": ">= 10.12.0" + } + }, + "node_modules/nopt": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "abbrev": "1" + }, + "bin": { + "nopt": "bin/nopt.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/npmlog": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz", + "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==", + "deprecated": "This package is no longer supported.", + "license": "ISC", + "optional": true, + "dependencies": { + "are-we-there-yet": "^3.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^4.0.3", + "set-blocking": "^2.0.0" + }, + "engines": { + "node": "^12.13.0 || ^14.15.0 || >=16.0.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/p-map": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz", + "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "aggregate-error": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "license": "MIT" + }, + "node_modules/path-to-regexp": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", + "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "license": "MIT", + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/pg-connection-string": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/pg-connection-string/-/pg-connection-string-2.6.2.tgz", + "integrity": "sha512-ch6OwaeaPYcova4kKZ15sbJ2hKb/VP48ZD2gE7i1J+L4MspCtBMAx8nMgz7bksc7IojCIIWuEhHibSMFH8m8oA==", + "license": "MIT" + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/promise-inflight": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz", + "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==", + "license": "ISC", + "optional": true + }, + "node_modules/promise-retry": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz", + "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "err-code": "^2.0.2", + "retry": "^0.12.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.1.tgz", + "integrity": "sha512-9G8cA+tuMS75+6G/TzW8OtLzmBDMo8p1JRxN5AZ+LAp8uxGA8V8GZm4GQ4/N5QNQEnLmg6SS7wyuSmbKepiKqA==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "http-errors": "2.0.0", + "iconv-lite": "0.7.0", + "unpipe": "1.0.0" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/raw-body/node_modules/iconv-lite": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.0.tgz", + "integrity": "sha512-cf6L2Ds3h57VVmkZe+Pn+5APsT7FpqJtEhhieDCvrE2MK5Qk9MyffgQyuxQTm6BChfeZNtcOLHp9IcWRVcIcBQ==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/rechoir": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.8.0.tgz", + "integrity": "sha512-/vxpCXddiX8NGfGO/mTafwjq4aFa/71pvamip0++IQk3zG8cbCj0fifNPrjjF1XMXUne91jL9OoxmdykoEtifQ==", + "license": "MIT", + "dependencies": { + "resolve": "^1.20.0" + }, + "engines": { + "node": ">= 10.13.0" + } + }, + "node_modules/resolve": { + "version": "1.22.10", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", + "integrity": "sha512-NPRy+/ncIMeDlTAsuqwKIiferiawhefFJtkNSW0qZJEqMEb+qBt/77B/jGeeek+F0uOeN05CDa6HXbbIgtVX4w==", + "license": "MIT", + "dependencies": { + "is-core-module": "^2.16.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/retry": { + "version": "0.12.0", + "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz", + "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "license": "ISC", + "optional": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/router": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", + "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", + "license": "MIT", + "dependencies": { + "debug": "^4.4.0", + "depd": "^2.0.0", + "is-promise": "^4.0.0", + "parseurl": "^1.3.3", + "path-to-regexp": "^8.0.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/semver": { + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/send/-/send-1.2.0.tgz", + "integrity": "sha512-uaW0WwXKpL9blXE2o0bRhoL2EGXIrZxQ2ZQ4mgcfoBxdFmQold+qWsD2jLrfZ0trjKL6vOw0j//eAwcALFjKSw==", + "license": "MIT", + "dependencies": { + "debug": "^4.3.5", + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "etag": "^1.8.1", + "fresh": "^2.0.0", + "http-errors": "^2.0.0", + "mime-types": "^3.0.1", + "ms": "^2.1.3", + "on-finished": "^2.4.1", + "range-parser": "^1.2.1", + "statuses": "^2.0.1" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/serve-static": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.0.tgz", + "integrity": "sha512-61g9pCh0Vnh7IutZjtLGGpTA355+OPn2TyDv/6ivP2h/AdAVX9azsoxmg2/M6nZeQZNYBEwIcsne1mJd9oQItQ==", + "license": "MIT", + "dependencies": { + "encodeurl": "^2.0.0", + "escape-html": "^1.0.3", + "parseurl": "^1.3.3", + "send": "^1.2.0" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC", + "optional": true + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC", + "optional": true + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks": { + "version": "2.8.7", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.7.tgz", + "integrity": "sha512-HLpt+uLy/pxB+bum/9DzAgiKS8CX1EvbWxI4zlmgGCExImLdiad2iCwXT5Z4c9c3Eq8rP2318mPW2c+QbtjK8A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ip-address": "^10.0.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz", + "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "agent-base": "^6.0.2", + "debug": "^4.3.3", + "socks": "^2.6.2" + }, + "engines": { + "node": ">= 10" + } + }, + "node_modules/sqlite3": { + "version": "5.1.7", + "resolved": "https://registry.npmjs.org/sqlite3/-/sqlite3-5.1.7.tgz", + "integrity": "sha512-GGIyOiFaG+TUra3JIfkI/zGP8yZYLPQ0pl1bH+ODjiX57sPhrLU5sQJn1y9bDKZUFYkX1crlrPfSYt0BKKdkog==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "dependencies": { + "bindings": "^1.5.0", + "node-addon-api": "^7.0.0", + "prebuild-install": "^7.1.1", + "tar": "^6.1.11" + }, + "optionalDependencies": { + "node-gyp": "8.x" + }, + "peerDependencies": { + "node-gyp": "8.x" + }, + "peerDependenciesMeta": { + "node-gyp": { + "optional": true + } + } + }, + "node_modules/ssri": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz", + "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "minipass": "^3.1.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "optional": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "optional": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tar": { + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", + "license": "ISC", + "dependencies": { + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/tar-fs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.3.tgz", + "integrity": "sha512-090nwYJDmlhwFwEW3QQl+vaNnxsO2yVsd45eTKRBzSzu+hlb1w2K9inVq5b0ngXuLVqQ4ApvsUHHnu/zQNkWAg==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-fs/node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar/node_modules/minipass": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==", + "license": "ISC", + "engines": { + "node": ">=8" + } + }, + "node_modules/tarn": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/tarn/-/tarn-3.0.2.tgz", + "integrity": "sha512-51LAVKUSZSVfI05vjPESNc5vwqqZpbXCsU+/+wxlOrUjk2SnFTt97v9ZgQrD4YmxYW1Px6w2KjaDitCfkvgxMQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/tildify": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/tildify/-/tildify-2.0.0.tgz", + "integrity": "sha512-Cc+OraorugtXNfs50hU9KS369rFXCfgGLpfCfvlc+Ud5u6VWmUQsOAa9HbTvheQdYnrdJqqv1e5oIqXppMYnSw==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.0.1.tgz", + "integrity": "sha512-OZs6gsjF4vMp32qrCbiVSkrFmXtG/AZhY3t0iAMrMBiAZyV9oALtXO8hsrHbMXF9x6L3grlFuwW2oAz7cav+Gw==", + "license": "MIT", + "dependencies": { + "content-type": "^1.0.5", + "media-typer": "^1.1.0", + "mime-types": "^3.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/unique-filename": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz", + "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==", + "license": "ISC", + "optional": true, + "dependencies": { + "unique-slug": "^2.0.0" + } + }, + "node_modules/unique-slug": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz", + "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==", + "license": "ISC", + "optional": true, + "dependencies": { + "imurmurhash": "^0.1.4" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "license": "ISC", + "optional": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "license": "ISC", + "optional": true, + "dependencies": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "license": "ISC" + } + } +} diff --git a/courses/backend/node/module-materials/package.json b/courses/backend/node/module-materials/package.json new file mode 100644 index 00000000..21c4ef0b --- /dev/null +++ b/courses/backend/node/module-materials/package.json @@ -0,0 +1,17 @@ +{ + "name": "node-module-materials", + "version": "1.0.0", + "description": "", + "license": "ISC", + "author": "", + "type": "module", + "main": "index.js", + "scripts": { + "dev": "node --watch index.js" + }, + "dependencies": { + "express": "^5.1.0", + "knex": "^3.1.0", + "sqlite3": "^5.1.7" + } +} From 76d2984e03343555c7296b2a6150627f4ea656c6 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 15 Sep 2025 21:12:21 +0200 Subject: [PATCH 06/21] chore: add info about parameters --- .../module-materials/examples/parameters.js | 20 ++++++++ courses/backend/node/week1/session-plan.md | 49 +++++++++++++------ 2 files changed, 53 insertions(+), 16 deletions(-) create mode 100644 courses/backend/node/module-materials/examples/parameters.js diff --git a/courses/backend/node/module-materials/examples/parameters.js b/courses/backend/node/module-materials/examples/parameters.js new file mode 100644 index 00000000..4e1612d3 --- /dev/null +++ b/courses/backend/node/module-materials/examples/parameters.js @@ -0,0 +1,20 @@ +import express from "express"; +const app = express(); + +// query parameters +// http://localhost:3000/query-parameters?hej=23,%20sd-p +app.get("/query-parameters", (req, res) => { + console.log(req.query); + res.send({ data: req.query }); +}); + +// URL parameters +// http://localhost:3000/parameters/apple-eater +app.get("/parameters/:username", (req, res) => { + console.log(req.params); + res.send({ data: req.params }); +}); + +app.listen(3000, function () { + console.log(`> Ready on http://localhost:3000`); +}); diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index 5703877a..90cb1b84 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -8,19 +8,20 @@ ## Session Outline - Express - - What is express - - Routing (focus on `get` requests) + - What is Express + - [Live coding: setup a server](./session-materials/01-server.md) + - Routing in Express - `app.use` - `app.get` - - [Live coding](#app-get-vs-app-use) - - Params `users/:id` - - Query parameters `users?limit=5` - - [Live coding](#query-parameters-vs-parameters) - - Exercise - - Route order - - [Live coding](#route-order) + - [Live coding: routing](#appget-vs-appuse) + - [Excercise: Setup routing](./session-materials/03-api.md) + - URL parameters in Express + - [Explanation and live coding](#query-parameters-vs-url-parameters) + - [Excercise: GET endpoints](./session-materials/05-get-endpoints.md) + - Route order + - [Live coding](#route-order) - Logging and debugging - - Focus on the students understanding **the order** in which things are executed + - Focus on the students understanding **the order** in which things are executed - Middleware - `next` method - Modifying `request` and `response` @@ -42,17 +43,33 @@ - [GET endpoints](./session-materials/05-get-endpoints.md) - [Authentication](./session-materials/06-auth.md) -## Code inspiration +## Live coding + +### `app.get` vs `app.use` + +Let's clarify the routing in Express using an [Express router](https://expressjs.com/en/starter/basic-routing.html). + +Code example can be found in the [module materials](../module-materials/index.js) and [router](../module-materials/examples/router.js). + +### Query parameters vs URL parameters + +#### URL parameters + +These are part of the URL path used to identify specific resources. + +**Example:** GET `/api/snippets/123` represented in our router by `/api/snippets/:id` + +In the above URL, `123` is an ID of the snippet to fetch. The route indicates that we will only return this one resouce or nothing else. `:id` is **dynamic**, so that we can define one route and be sure to get and parse any ID passed. In the same time, passing an `:id` is **required**. -### app get vs app use +#### Query parameters -TODO - What to do with live coding exercises? #122 +These come after the `?` character and are usually key=value pairs, separated by `&`. -Go to the `teacher-live-coding` [repo](https://github.com/HackYourFuture-CPH/teacher-live-coding), run `npm install` and run using `nodemon ./src/backend/app-use-vs-app-get.js`. Try and implement this functionality from the bottom while explaining. +**Example:** GET `/api/snippets?tag=popular` -### Query parameters vs parameters +Used for sorting, filtering, pagination, or tracking, are usually **optional**. Can also be multiple values. -Run `nodemon ./src/backend/query-parameters-vs-parameters.js`. Try and implement this functionality from the bottom while explaining. +Code example can be found in the [parameters file](../module-materials/examples/parameters.js). ### Route order From a3187a20a367ff8aac5538b93a514ad67b039273 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 15 Sep 2025 21:21:42 +0200 Subject: [PATCH 07/21] chore: add route order example --- .../module-materials/examples/route-order.js | 26 +++++++++++++++++++ courses/backend/node/week1/session-plan.md | 17 +++++++++--- 2 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 courses/backend/node/module-materials/examples/route-order.js diff --git a/courses/backend/node/module-materials/examples/route-order.js b/courses/backend/node/module-materials/examples/route-order.js new file mode 100644 index 00000000..0c0b92ad --- /dev/null +++ b/courses/backend/node/module-materials/examples/route-order.js @@ -0,0 +1,26 @@ +import express from "express"; +const app = express(); + +app.get("/data", (_req, res) => { + res.send({ data: 1 }); +}); + +app.get("/data", (_req, res) => { + res.send({ data: 2 }); +}); + +////// which one is being called when? + +app.get("/data/:id", (req, res) => { + console.log("Parametrized URL"); + res.send({ data: req.params }); +}); + +app.get("/data/overview", (req, res) => { + console.log("Overview"); + res.send({ data: req.params }); +}); + +app.listen(3000, function () { + console.log(`> Ready on http://localhost:3000`); +}); diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index 90cb1b84..7c85b132 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -19,9 +19,8 @@ - [Explanation and live coding](#query-parameters-vs-url-parameters) - [Excercise: GET endpoints](./session-materials/05-get-endpoints.md) - Route order - - [Live coding](#route-order) + - [Live coding: why route order matters](#route-order) - Logging and debugging - - Focus on the students understanding **the order** in which things are executed - Middleware - `next` method - Modifying `request` and `response` @@ -51,6 +50,10 @@ Let's clarify the routing in Express using an [Express router](https://expressjs Code example can be found in the [module materials](../module-materials/index.js) and [router](../module-materials/examples/router.js). +```js +node --watch index.js +``` + ### Query parameters vs URL parameters #### URL parameters @@ -71,9 +74,17 @@ Used for sorting, filtering, pagination, or tracking, are usually **optional**. Code example can be found in the [parameters file](../module-materials/examples/parameters.js). +```js +node --watch examples/parameters.js +``` + ### Route order -Run `nodemon ./src/backend/route-order.js`. Try and implement this functionality from the bottom while explaining. +Code example can be found in the [route order file](../module-materials/examples/route-order.js) + +```js +node --watch examples/route-order.js +``` ### Middleware From 4bd0fac65baa7e1053e975461c3c98518cf5b987 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magdalena=20Odrow=C4=85=C5=BC-=C5=BBelezik?= Date: Mon, 6 Oct 2025 19:27:33 +0200 Subject: [PATCH 08/21] Update courses/backend/node/week1/assignment.md Co-authored-by: Marco Richetta --- courses/backend/node/week1/assignment.md | 4 ---- 1 file changed, 4 deletions(-) diff --git a/courses/backend/node/week1/assignment.md b/courses/backend/node/week1/assignment.md index 7241edb5..e037de22 100644 --- a/courses/backend/node/week1/assignment.md +++ b/courses/backend/node/week1/assignment.md @@ -198,7 +198,3 @@ Your usage of Knex should be getting a bit more advanced now. You will move from Check out the [Knex cheatsheet](https://devhints.io/knex)! -## Hand in homework - -Need to brush up on the homework hand-in process? -Check [this resource](https://github.com/HackYourFuture-CPH/Git/blob/main/homework-submission.md) to remember how to hand in the homework correctly! From 887a414bff4b4f9125cb17eab951b4070ab73128 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magdalena=20Odrow=C4=85=C5=BC-=C5=BBelezik?= Date: Mon, 6 Oct 2025 19:27:42 +0200 Subject: [PATCH 09/21] Update courses/backend/node/week1/session-materials/01-server.md Co-authored-by: Marco Richetta --- courses/backend/node/week1/session-materials/01-server.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courses/backend/node/week1/session-materials/01-server.md b/courses/backend/node/week1/session-materials/01-server.md index af00580d..317e86ae 100644 --- a/courses/backend/node/week1/session-materials/01-server.md +++ b/courses/backend/node/week1/session-materials/01-server.md @@ -3,8 +3,8 @@ Create a new, separate folder somewhere on your machine: ```shell -mkdir nodejs-week2 -code nodejs-week2 # to open the folder in VS Code +mkdir nodejs-week1 +code nodejs-week1 # to open the folder in VS Code ``` You can also reuse some other folder. Main thing to watch out for is that the folder you decide to use should ideally be empty. From c60a9404773f822aab768797e4d7842661f398fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magdalena=20Odrow=C4=85=C5=BC-=C5=BBelezik?= Date: Mon, 6 Oct 2025 19:27:50 +0200 Subject: [PATCH 10/21] Update courses/backend/node/week1/session-materials/02-schema.md Co-authored-by: Marco Richetta --- courses/backend/node/week1/session-materials/02-schema.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courses/backend/node/week1/session-materials/02-schema.md b/courses/backend/node/week1/session-materials/02-schema.md index 67a5a71a..5e01d1cc 100644 --- a/courses/backend/node/week1/session-materials/02-schema.md +++ b/courses/backend/node/week1/session-materials/02-schema.md @@ -2,10 +2,10 @@ This week we will work with 2 tables: -- A `users` table similar to week 1 but now with an added `token` column. +- A `users` table. - A `snippets` table containing (code) snippets that belong to a user. -Create a new database/schema `hyf_node_week2` containing the following tables: +Create a new database/schema `hyf_node_week1` containing the following tables: ```sql CREATE TABLE `users` ( From b2175a2a99d5e13c93895a8eb3a674092e560513 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 6 Oct 2025 19:41:28 +0200 Subject: [PATCH 11/21] chore: add middleware code --- .../module-materials/examples/middleware.js | 15 +++++++++++++ courses/backend/node/week1/session-plan.md | 21 ++++++++++++------- 2 files changed, 29 insertions(+), 7 deletions(-) create mode 100644 courses/backend/node/module-materials/examples/middleware.js diff --git a/courses/backend/node/module-materials/examples/middleware.js b/courses/backend/node/module-materials/examples/middleware.js new file mode 100644 index 00000000..afe9e963 --- /dev/null +++ b/courses/backend/node/module-materials/examples/middleware.js @@ -0,0 +1,15 @@ +import express from "express"; +const app = express(); + +app.use((req, _res, next) => { + console.log(req.headers["accept-language"]); + const isFromDenmark = req.headers["accept-language"].includes("da"); + console.log(isFromDenmark); + req.isFromDenmark = isFromDenmark; + + next(); +}); + +app.listen(3000, function () { + console.log(`> Ready on http://localhost:3000`); +}); diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index 7c85b132..1e1cf36f 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -22,16 +22,15 @@ - [Live coding: why route order matters](#route-order) - Logging and debugging - Middleware - - `next` method + - [`next` method](https://expressjs.com/en/guide/using-middleware.html) - Modifying `request` and `response` - - - [Live coding](#middleware) - - Exercise + - [Live coding: basic middleware example](#middleware) - Postman - Create collection and save queries - - Sending get requests requests - - Query parameters - - Parameters + - Sending `GET` requests + - Query parameters in Postman + - URL parameters in Postman ## Exercises @@ -88,4 +87,12 @@ node --watch examples/route-order.js ### Middleware -Run using `nodemon ./src/backend/middleware.js`. Try and implement this functionality from the bottom while explaining. +Middleware is a generic term used for any software that acts as a connector between two other bits of software. In Express world, middleware is a layer that can be injected between a request and response and perform additional tasks. + +You can find out more about Express middleware in the [Express middleware documentation](https://expressjs.com/en/guide/using-middleware.html). + +Code example of a sample middleware can be found in the [middleware file](../module-materials/examples/middleware.js) + +```js +node --watch examples/middleware.js +``` From c5c14e62d096f8b1c68937e17ac5b1181e359d1e Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 6 Oct 2025 20:14:40 +0200 Subject: [PATCH 12/21] chore: move week2 to a separate PR --- courses/backend/node/README.md | 8 +- courses/backend/node/week1/README.md | 4 +- courses/backend/node/week1/assignment.md | 1 - courses/backend/node/week2/README.md | 46 ----- courses/backend/node/week2/assignment.md | 208 --------------------- courses/backend/node/week2/preparation.md | 5 - courses/backend/node/week2/session-plan.md | 54 ------ 7 files changed, 6 insertions(+), 320 deletions(-) diff --git a/courses/backend/node/README.md b/courses/backend/node/README.md index aabe9f91..ddd5ab78 100644 --- a/courses/backend/node/README.md +++ b/courses/backend/node/README.md @@ -4,10 +4,10 @@ This module is part of the Backend specialism and focuses on using Node.js to bu ## Contents -| Week | Topic | Preparation | Lesson Plan | Assignment | -| ---- | ------------------------ | ----------------------------------- | ------------------------------------ | ----------------------------------- | -| 1. | Express | [Preparation](week1/preparation.md) | [Homework](week1/homework/README.md) | [Lesson plan](week1/lesson-plan.md) | -| 2. | Database connection; API | [Preparation](week2/preparation.md) | [Homework](week2/homework/README.md) | [Lesson plan](week2/lesson-plan.md) | +| Week | Topic | Preparation | Lesson Plan | Assignment | +| ---- | ------------------------ | ----------------------------------- | ----------------------------------- | ------------------------------------- | +| 1. | Express | [Preparation](week1/preparation.md) | [Assignment](./week1/assignment.md) | [Session plan](week1/session-plan.md) | +| 2. | Database connection; API | [Preparation](week2/preparation.md) | [Assignment](./week1/assignment.md) | [Session plan](week2/session-plan.md) | ## Module Learning Goals diff --git a/courses/backend/node/week1/README.md b/courses/backend/node/week1/README.md index 8978e456..8ea880f5 100644 --- a/courses/backend/node/week1/README.md +++ b/courses/backend/node/week1/README.md @@ -64,8 +64,8 @@ There are a few good extensions or middleware that is easy to plug into express ### Relevant links - [Preparation](preparation.md) -- [Homework](homework/README.md) -- [Lesson plan](lesson-plan.md) +- [Assignment](./assignment.md) +- [Session plan](./session-plan.md) ### Express.js diff --git a/courses/backend/node/week1/assignment.md b/courses/backend/node/week1/assignment.md index e037de22..0d534f90 100644 --- a/courses/backend/node/week1/assignment.md +++ b/courses/backend/node/week1/assignment.md @@ -197,4 +197,3 @@ Your usage of Knex should be getting a bit more advanced now. You will move from - `.del` (for deletion) Check out the [Knex cheatsheet](https://devhints.io/knex)! - diff --git a/courses/backend/node/week2/README.md b/courses/backend/node/week2/README.md index 0d67f92b..e69de29b 100644 --- a/courses/backend/node/week2/README.md +++ b/courses/backend/node/week2/README.md @@ -1,46 +0,0 @@ -# Session plan (Week 2) - -In this session we will focus on connecting to a database, building an API, and using Postman to test our API endpoints. We will also cover how to structure our code for better maintainability and scalability. - -## Contents - -- [Preparation](./preparation.md) -- [Session Plan](./session-plan.md) (for mentors) -- [Assignment](./assignment.md) - -## Session Learning goals - -By the end of this session, you will be able to: -TODO - Format as `verb` - -- [ ] Database interaction - - [ ] Connecting to mysql using Knex - - [ ] Environment variables - - [ ] Executing queries using knex -- [ ] API - - [ ] REST - - [ ] CRUD - - [ ] Router verb `GET`, `POST`, `DELETE`, `PUT` - - [ ] POST mention express.json middleware - - [ ] Postman - -TODO - Move this content somewhere else - -### 1. What is Representational State Transfer (REST)? - -Building software is like building houses: architecture is everything. The design of each part is just as important as the utility of it. REST is a specific architectural style for web applications. It serves to organise code in **predictable** ways. - -The most important features of REST are: - -- An application has a `frontend` (client) and a `backend` (server). This is called [separation of concerns](https://medium.com/machine-words/separation-of-concerns-1d735b703a60): each section has its specific job to do. The frontend deals with presenting data in a user friendly way, the backend deals with all the logic and data manipulation -- The server is `stateless`, which means that it doesn't store any data about a client session. Whenever a client sends a request to the server, each request from the client to server must contain all of the information necessary to understand the request, and cannot take advantage of any stored context on the server. This makes it possible to handle requests from millions of users. -- Server responses can be temporarily stored on the client (a browser) using a process called `caching`: storing files like images or webpages in the browser to load the next time you enter a website (instead of getting them from the server, which generally takes longer to do). -- Client-server communication is done through `Hypertext Transfer Protocol (HTTP)` (more on that later), which serves as the style (the how) of communication. - -It's important to know about REST because it teaches us how web applications are designed and holds us to a standard that makes development and usage predictable. However, don't worry if you don't know what any of this means just yet. It's good to be exposed to it, and understanding will come with experience. - -For more research, check the following resource: - -- [What is REST: a simple explanation for beginners](https://medium.com/extend/what-is-rest-a-simple-explanation-for-beginners-part-1-introduction-b4a072f8740f) - -- [@NoerGitKat (lots of web app clones/examples to learn from)](https://github.com/NoerGitKat) diff --git a/courses/backend/node/week2/assignment.md b/courses/backend/node/week2/assignment.md index c8223d69..e69de29b 100644 --- a/courses/backend/node/week2/assignment.md +++ b/courses/backend/node/week2/assignment.md @@ -1,208 +0,0 @@ -# Assignment - -Once again, you will deliver 2 pull requests: - -- A pull request for the **Warmup** - in your regular hyf-homework repository -- A pull request for the additional **meal sharing endpoints** - in the meal-sharing repository - -In both repositories, create a `nodejs-week2` branch from `main` to work on the homework (`git checkout -b nodejs-week2` ). - -## Warmup - -For the warmup you will be handed a Contacts API with a single endpoint: - -- `GET /api/contacts` - -This endpoint accepts a query parameter `sort`. Here's how you can use it: - -- `GET /api/contacts?sort=first_name%20ASC` - - Sorts contacts by first name, ascending -- `GET /api/contacts?sort=last_name%20DESC` - - Sorts contacts by last name, descending - -But this `sort` query parameter has been introduced with a SQL injection vulnerability and the goal is to demonstrate the issue and then fix and remove the vulnerability. - -### Setup - -TODO - Review assignment to work with sqlite. - -Go to `nodejs/week2` in your `hyf-homework` repo: - -```shell -npm init -y -npm i express sqlite3 knex -npm set-script dev "node --watch app.js" -``` - -Make sure you have `"type": "module"` in your `package.json`. - -You should also ensure that the `node_modules/` folder is ignored by Git: - -```shell -echo node_modules/ >> .gitignore -``` - -Create a database/schema called `hyf_node_week2_warmup` with a `contacts` table: - -```sql -CREATE TABLE `contacts` ( - `id` int unsigned NOT NULL AUTO_INCREMENT, - `first_name` varchar(255) NOT NULL, - `last_name` varchar(255) NOT NULL, - `email` varchar(255) DEFAULT NULL, - `phone` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`) -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; - --- Sample data -insert into contacts (id, first_name, last_name, email, phone) values (1, 'Selig', 'Matussov', 'smatussov0@pinterest.com', '176-630-4577'); -insert into contacts (id, first_name, last_name, email, phone) values (2, 'Kenny', 'Yerrington', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (3, 'Emilie', 'Gaitskell', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (4, 'Jordon', 'Tokell', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (5, 'Sallyann', 'Persse', 'spersse4@webnode.com', '219-157-2368'); -insert into contacts (id, first_name, last_name, email, phone) values (6, 'Berri', 'Bulter', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (7, 'Lanni', 'Ivanilov', 'livanilov6@fda.gov', null); -insert into contacts (id, first_name, last_name, email, phone) values (8, 'Dagny', 'Milnthorpe', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (9, 'Annadiane', 'Bansal', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (10, 'Tawsha', 'Hackley', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (11, 'Rubetta', 'Ozelton', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (12, 'Charles', 'Boughey', 'cbougheyb@senate.gov', '605-358-5664'); -insert into contacts (id, first_name, last_name, email, phone) values (13, 'Shantee', 'Robbe', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (14, 'Gleda', 'Peat', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (15, 'Arlinda', 'Ethersey', 'aetherseye@biglobe.ne.jp', '916-139-1300'); -insert into contacts (id, first_name, last_name, email, phone) values (16, 'Armando', 'Meachem', 'ameachemf@oaic.gov.au', '631-442-5339'); -insert into contacts (id, first_name, last_name, email, phone) values (17, 'Codi', 'Redhouse', null, '401-953-6897'); -insert into contacts (id, first_name, last_name, email, phone) values (18, 'Ann', 'Buncombe', 'abuncombeh@ow.ly', '210-338-0748'); -insert into contacts (id, first_name, last_name, email, phone) values (19, 'Louis', 'Matzkaitis', 'lmatzkaitisi@ebay.com', '583-996-6979'); -insert into contacts (id, first_name, last_name, email, phone) values (20, 'Jessey', 'Pala', null, null); -insert into contacts (id, first_name, last_name, email, phone) values (21, 'Archy', 'Scipsey', 'ascipseyk@ask.com', '420-983-2426'); -insert into contacts (id, first_name, last_name, email, phone) values (22, 'Benoit', 'Mould', 'bmouldl@bing.com', '271-217-9218'); -insert into contacts (id, first_name, last_name, email, phone) values (23, 'Sherm', 'Girardey', 'sgirardeym@guardian.co.uk', '916-999-2957'); -insert into contacts (id, first_name, last_name, email, phone) values (24, 'Raquel', 'Mudge', 'rmudgen@slate.com', '789-830-7473'); -insert into contacts (id, first_name, last_name, email, phone) values (25, 'Tabor', 'Reavey', null, null); -``` - -Create `app.js`: - -```js -import knex from "knex"; -import express from "express"; - -const dbFile = "PATH_TO_YOUR_SQLITE_DB"; - -const knexInstance = knex({ - client: "sqlite3", - connection: { - filename: dbFile, - }, -}); - -const app = express(); -const port = process.env.PORT || 3000; - -app.use(express.json()); - -const apiRouter = express.Router(); -app.use("/api", apiRouter); - -const contactsAPIRouter = express.Router(); -apiRouter.use("/contacts", contactsAPIRouter); - -contactsAPIRouter.get("/", async (req, res) => { - let query = knexInstance.select("*").from("contacts"); - - if ("sort" in req.query) { - const orderBy = req.query.sort.toString(); - if (orderBy.length > 0) { - query = query.orderByRaw(orderBy); - } - } - - console.log("SQL", query.toSQL().sql); - - try { - const data = await query; - res.json({ data }); - } catch (e) { - console.error(e); - res.status(500).json({ error: "Internal server error" }); - } -}); - -app.listen(port, () => { - console.log(`Listening on port ${port}`); -}); -``` - -As mentioned above, the `sort` query parameter has been introduced with a SQL injection vulnerability. - -First, you should demonstrate the SQL injection and that it for instance is possible to drop/delete the `contacts` table with the `sort` query parameter. -You can for instance demonstrate this with a screen recording and include it in the PR description. - -After having demonstrated the SQL injection vulnerability, the goal is then to fix the issue by updating `app.js`. - -**Hint:** the `multipleStatements: true` part in the configuration indicates how you can use the vulnerability. The configuration should not be changed though, the SQL injection should be fixed by making changes in the `/api/contacts` route. - -## Meal sharing endpoints - -You will continue working in the meal-sharing repository for this task. - -You should have the basic [CRUD](https://www.freecodecamp.org/news/crud-operations-explained/) endpoints for **meals** and **reservations** as the result of last week's homework. This week, you will add **query parameters**, that will allow you to **sort** and **filter** the information retrieved from the database. - -### Routes - -#### Meals - -Work with your `GET api/meals` route to add the query parameters. -Make sure that the query parameters can be combined, f.x. `?limit=4&maxPrice=90`. - -| Parameter | Data type | Description | Example | -| ----------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | -| `maxPrice` | Number | Returns all meals that are cheaper than `maxPrice`. | `api/meals?maxPrice=90` | -| `availableReservations` | Boolean | Returns all meals that still have available spots left, if `true`. If `false`, return meals that have no available spots left.[^1] | `api/meals?availableReservations=true` | -| `title` | String | Returns all meals that partially match the given title. `Rød grød` will match the meal with the title `Rød grød med fløde`. | `api/meals?title=Indian%20platter` | -| `dateAfter` | Date | Returns all meals where the date for `when` is after the given date. | `api/meals?dateAfter=2022-10-01` | -| `dateBefore` | Date | Returns all meals where the date for `when` is before the given date. | `api/meals?dateBefore=2022-08-08` | -| `limit` | Number | Returns the given number of meals. | `api/meals?limit=7` | -| `sortKey`[^2] | String | Returns all meals sorted by the given key. Allows `when`, `max_reservations` and `price` as keys. Default sorting order is asc(ending). | `api/meals?sortKey=price` | -| `sortDir`[^3] | String | Returns all meals sorted in the given direction. Only works combined with the `sortKey` and allows `asc` or `desc`. | `api/meals?sortKey=price&sortDir=desc` | - -[^1]: `availableReservations` requires you to work with several database tables at once. Try practicing the right query in MySQL Workbench first (you might have it from Database week2 homework) and once you have it working, build it with `knex`. - -[^2]: This used to be `sort_key` in a previous version of the homework text. - -[^3]: This used to be `sort_dir` in a previous version of the homework text. - -#### Reviews - -By now, you have the basic set of endpoints for **meals** and **reservations** and even a collection of query parameters for **meals**. To practice a bit more and finalize the basic backend functionality, create the set of routes for **reviews**: - -| Route | HTTP method | Description | -| ----------------------------- | ----------- | ---------------------------------------- | -| `/api/reviews` | GET | Returns all reviews. | -| `/api/meals/:meal_id/reviews` | GET | Returns all reviews for a specific meal. | -| `/api/reviews` | POST | Adds a new review to the database. | -| `/api/reviews/:id` | GET | Returns a review by `id`. | -| `/api/reviews/:id` | PUT | Updates the review by `id`. | -| `/api/reviews/:id` | DELETE | Deletes the review by `id`. | - -#### Knex - -You should try to avoid using `knex.raw` and instead use the different `knex` functions, for example: - -- `.select`, `.from`, `.where`, `join`, `leftJoin` -- `.insert` -- `.update` -- `.del` (for deletion) - -Check out the [Knex cheatsheet](https://devhints.io/knex)! - -## Hand in homework - -Need to brush up on the homework hand-in process? - -Check [this resource](https://github.com/HackYourFuture-CPH/Git/blob/main/homework-submission.md) to remember how to hand in the homework correctly! - -## Feedback - -And finally, please take two minutes to answer the survey [here](https://forms.gle/YG5KCnSCPhb8dJAL9) to give feedback to the staff and mentors. diff --git a/courses/backend/node/week2/preparation.md b/courses/backend/node/week2/preparation.md index 6196cd78..e69de29b 100644 --- a/courses/backend/node/week2/preparation.md +++ b/courses/backend/node/week2/preparation.md @@ -1,5 +0,0 @@ -# Preparation - -- [NodeJS Web API with KNEX and Express](https://www.youtube.com/watch?v=QNw9q4YXR4E) (15 min) -- up until the `The Visual Studio Code REST client` section (15 min) -- - Free API for testing and prototyping. (5 min) diff --git a/courses/backend/node/week2/session-plan.md b/courses/backend/node/week2/session-plan.md index 9fef4751..e69de29b 100644 --- a/courses/backend/node/week2/session-plan.md +++ b/courses/backend/node/week2/session-plan.md @@ -1,54 +0,0 @@ -# Lesson plan - -- Focus on having lots of in class exercises. -- DON'T teach everything, let the students investigate topics on their own as well! -- Focus on how to read documentation, google answers and google errors!! -- Teach towards the students being able to solve the homework. - -Remember to add the code you wrote in the class to the relevant class branch's class work folder. If the branch has not been created just create and push it :) If you don't have access, write to one from the core team. You can see an example below! - -If you find anything that could be improved then please create a pull request! We welcome changes, so please get involved if you have any ideas!!! - ---- - -- Database interaction - - Connecting to mysql using knex - - Executing queries - - `select`, `create`. You could let the students figure out how `delete` and `update` works - - [Code inspiration](#phonebook-database) especially focus on the promise and query part -- API - - REST - - CRUD - - Router verb `GET`, `POST`, `DELETE`, `PUT` - - Especially focus on post with `app.use(express.urlencoded({ extended: true }));` and `app.use(express.json());` - - [Code inspiration](#phonebook-api) -- Postman - - `POST`, `DELETE`, `PUT` requests -- Exercise finish concerts api - -## Code inspiration - -### Phonebook database - -- Go to the `teacher-live-coding` [repo](https://github.com/HackYourFuture-CPH/teacher-live-coding), to the relevant folder -- Copy the `.env.example` and rename the copied file to `.env` -- Run `npm install` -- Start the application by running `node --watch ./src/backend/phonebook-database-queries.js` - -Try and implement this functionality from the bottom while explaining. - -### Phonebook api - -Start the application by running `node --watch ./src/backend/create-an-api.js`. - -The following two routes have been created, get help by the students to create some of the other routes. - -| Url | Verb | Functionality | Example | -| ------------------- | ------ | --------------------------- | -------------------- | -| `api/contacts/` | GET | Returns all contacts | `GET api/contacts/` | -| `api/contacts/` | POST | Adds a new contact | `POST api/contacts/` | -| `api/contacts/{id}` | GET | Returns contact by `id` | `GET api/contacts/2` | -| `api/contacts/{id}` | PUT | Updates the contact by `id` | `PUT api/contacts/2` | -| `api/contacts/{id}` | DELETE | Deletes the contact by `id` | `DELETE contacts/2` | - -Thank you very much for teaching NodeJS. Please don't hesitate to give feedback by clicking [here](https://forms.gle/sAuVhsTmJ1qSmjgJ6) (teachers and teacher assistants). For homework reviewers, please access the survey [here](https://forms.gle/nVbX9ShusF2a5Aa87). From 2f96c6042c124b132ac7f598dbd11093c3952d2b Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 8 Oct 2025 19:35:36 +0200 Subject: [PATCH 13/21] chore: translated sql to sqlite --- .../node/module-materials/snippets.sqlite3 | Bin 0 -> 24576 bytes .../node/week1/session-materials/02-schema.md | 75 ++++++++++++------ 2 files changed, 50 insertions(+), 25 deletions(-) create mode 100644 courses/backend/node/module-materials/snippets.sqlite3 diff --git a/courses/backend/node/module-materials/snippets.sqlite3 b/courses/backend/node/module-materials/snippets.sqlite3 new file mode 100644 index 0000000000000000000000000000000000000000..0cc7d0d8bf1381838d825791c686eebd5940861e GIT binary patch literal 24576 zcmeI4&u<$=6vub{Bfpe*kr1*%37QI3#7eSw?W8|kRjTW55`*KU{s9T1vL@?EJoS3l z+1YIpjul)Gcg}D@2>MrWfl3^?b3q(9aVSFSg*W54N-C#FRDh7)Nb8+n@4Ih4vwPU_ z&a2<4@Q{%`Kj_hrDPE@OZz^LNPp# zm1=geVUtGjR>da6S~5Q~MF?+`a#)L{>Lzc6qI;1~q@;Muybv zRU7;)*>xi81exdU4B4!Lam5BwHE_OGE;VMRGFyqU^umI^Ga!55!W&~tu!D$s4ijgK z$+Mi#HqLvr#}>gd^I%>dPp8xRM{g-j<$(H$ov}Z0?BAOt$M^Pl(0gW%hf^!(SrLUk zB5cXgjF=dgp5MlLl)GnbNV8hL)3gV-f!}4`pm|UW@H6qT^o_Kxaj(sePH598^i{c~ z2(@zRl*q*Xx~rwS#^FJmkPq7o9Ldy%W_)FArXHt0gDfT_fCP{L5x>>zm&a3veydBQoX`yZ}ZKP?|FXULf-UYodEKumD|zo zm8;jbV^TSN;W^5=B@3V|7jijJ-hkyBe~kvtw+|`&0ndDM9}1kFR_@XE^2+6=u6&-o ze!kDvl9eM?wvf+)a*=oa2K8u{QplO1%AS@D{jQbEPr-~Pjh<%wY zS$**FL{er4UpgdYcJOIJTxJK~62vCsvy+4W|K<1pG2<7_cnm+7kN^@u0!RP}AOR$R z1dsp{Kmter2_S+0GlA(?d`=%my_lNR<7r*R0Z7XC|EcMRn(?_&PW_hpGF6*-GV{$$ zZTbmRVnPB)00|%gB!C2v01`j~NB{}^R|G0k<62(N)F+je zF3C`R+}kIV2-tA$K5Zb}BdV*}oRn(T-N{jE4T#fr2qD%;z2gU=6NREF*k0t4DC7|T z?wB}mIl+XCoK6lR?sQ3;3BK={+Y?fDfsb^uRIih@0HOd^=&>J#>;54LeX<*Iw@m^j z`o1UleMVf>D##%ZJ7j2MZj7tpH%B=fuW&CqA|=1qqh4Fcv+ohnVK8*qRj(?Que)rY zdmhC5Gn->dj*W6OUW-IXHV9-7;rUdj?1V=s5tf<@E%Oo6H@eIebmrn zw|%F}0@(R()mfJfekageh3R{kpd-4{7 zH|2UL<17MMS+Mu^7<5yUa}v1#bYq}9dGTPBhxI!Za*GP?h@uDgUl{o92u2MIVe;z` zZn4x@B=4<5IL1YFjkh80q`XhT&b+Hj-GykBzeNQII&K|@9p5XS+=%-TZ!;+!3H1cX VUffq0fQl0Z0fdw!Av`X^{1aE8%SiwL literal 0 HcmV?d00001 diff --git a/courses/backend/node/week1/session-materials/02-schema.md b/courses/backend/node/week1/session-materials/02-schema.md index 5e01d1cc..aaf3493f 100644 --- a/courses/backend/node/week1/session-materials/02-schema.md +++ b/courses/backend/node/week1/session-materials/02-schema.md @@ -5,31 +5,56 @@ This week we will work with 2 tables: - A `users` table. - A `snippets` table containing (code) snippets that belong to a user. -Create a new database/schema `hyf_node_week1` containing the following tables: +Create a new Sqlite database/schema `hyf_node_week1` containing the following tables: ```sql -CREATE TABLE `users` ( - `id` int unsigned NOT NULL AUTO_INCREMENT, - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `confirmed_at` datetime DEFAULT NULL, - `first_name` varchar(255) NOT NULL, - `last_name` varchar(255) NOT NULL, - `email` varchar(255) NOT NULL, - `token` varchar(255) DEFAULT NULL, - PRIMARY KEY (`id`), - UNIQUE KEY `email_UNIQUE` (`email`), - UNIQUE KEY `token_UNIQUE` (`token`) -) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8mb4; - -CREATE TABLE `snippets` ( - `id` int unsigned NOT NULL AUTO_INCREMENT, - `created_at` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, - `user_id` int unsigned NOT NULL, - `title` varchar(255) NOT NULL, - `contents` text NOT NULL, - `is_private` tinyint NOT NULL DEFAULT '1', - PRIMARY KEY (`id`), - KEY `id_idx` (`user_id`), - CONSTRAINT `id` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`) ON DELETE RESTRICT -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; +CREATE TABLE users ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP), + confirmed_at DATETIME DEFAULT NULL, + first_name TEXT NOT NULL, + last_name TEXT NOT NULL, + email TEXT NOT NULL UNIQUE, + token TEXT UNIQUE +); + +CREATE TABLE snippets ( + id INTEGER PRIMARY KEY AUTOINCREMENT, + created_at DATETIME NOT NULL DEFAULT (CURRENT_TIMESTAMP), + user_id INTEGER NOT NULL, + title TEXT NOT NULL, + contents TEXT NOT NULL, + is_private INTEGER NOT NULL DEFAULT 1, + FOREIGN KEY (user_id) REFERENCES users (id) ON DELETE RESTRICT +); + +``` + +Insert sample data to have something to play with: + +```sql +INSERT INTO users (first_name, last_name, email, token, confirmed_at) +VALUES +('Aiko', 'Tanaka', 'aiko.tanaka@example.com', 'tok123', '2025-01-12 10:30:00'), +('Mateo', 'García', 'mateo.garcia@example.com', 'mad456', '2025-02-18 14:22:00'), +('Liam', "O'Connor", 'liam.oconnor@example.com', 'dub789', '2025-03-09 09:45:00'), +('Fatima', 'Al-Sayed', 'fatima.alsayed@example.com', 'cai321', '2025-04-01 16:15:00'), +('Zanele', 'Khumalo', 'zanele.khumalo@example.com', 'jhb654', NULL); -- Not yet confirmed + + +INSERT INTO snippets (user_id, title, contents, is_private) +VALUES +(1, 'Async in Python', 'A quick guide to using asyncio for concurrent tasks.', 0), +(1, 'SQL Basics', 'An introduction to SELECT, WHERE, and JOIN in SQL.', 1), + +(2, 'React Hooks', 'Explaining useState and useEffect with examples.', 0), +(2, 'Docker 101', 'Setting up containers for web apps.', 1), +(2, 'Node.js Tips', 'Best practices for writing clean async code.', 0), + +(3, 'Rust Ownership', 'A simple explanation of the ownership model.', 0), +(3, 'Linux Commands', 'Common shell commands for beginners.', 1), + +(4, 'CSS Grid Layout', 'How to build responsive layouts with CSS Grid.', 0), +(4, 'Tailwind Shortcuts', 'Useful utility classes for quick design.', 1), +(4, 'Flask REST API', 'Creating a small REST API in Flask.', 0); ``` From d1ecddc5a63df7c7cc0153775006999243bfc3b6 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 8 Oct 2025 20:25:33 +0200 Subject: [PATCH 14/21] chore: reorder content --- .../{03-api.md => 03-routing.md} | 32 ++++++------ .../04-database-connection.md | 52 +++++++++++++++++++ .../session-materials/05-get-endpoints.md | 2 +- courses/backend/node/week1/session-plan.md | 29 ++++++----- .../session-materials/04-post-endpoint.md | 28 ---------- 5 files changed, 84 insertions(+), 59 deletions(-) rename courses/backend/node/week1/session-materials/{03-api.md => 03-routing.md} (58%) create mode 100644 courses/backend/node/week1/session-materials/04-database-connection.md rename courses/backend/node/{week1 => week2}/session-materials/04-post-endpoint.md (68%) diff --git a/courses/backend/node/week1/session-materials/03-api.md b/courses/backend/node/week1/session-materials/03-routing.md similarity index 58% rename from courses/backend/node/week1/session-materials/03-api.md rename to courses/backend/node/week1/session-materials/03-routing.md index f34b851f..464d405b 100644 --- a/courses/backend/node/week1/session-materials/03-api.md +++ b/courses/backend/node/week1/session-materials/03-routing.md @@ -1,11 +1,14 @@ -# API +# Routing + +## End goal structure The end goal of the exercise is to implement the following routes: -- `POST /api/snippets` to create a snippet - `GET /api/snippets` to get a list of snippets - `GET /api/snippets/:id` to get a single snippet +## Create Snippets router + We will create the snippet routes in a different file, `api/snippets.js`, which will export an [Express router](https://expressjs.com/en/starter/basic-routing.html). That will look something like this: @@ -19,37 +22,34 @@ const router = express.Router(); // GET /api/snippets router.get("/", async (request, response) => { // TODO + console.log("Hello from Snippets"); }); -// TODO: POST /api/snippets // TODO: GET /api/snippets/:id export default router; ``` -We will also have the database connection in a separate file: +## Add the router to the main server + +Next thing in the setup is to actually use the router we're exporting from `api/snippets.js`. +Inside `app.js`, below setting up Express, add your new snippets router: ```js -// Contents of database.js +import snippetsRouter from "./api/snippets.js"; -import knex from "knex"; +app.use("/api/snippets", snippetsRouter); +``` -const dbFile = "PATH_TO_YOUR_SQLITE_DB"; +## Test the app -const knexInstance = knex({ - client: "sqlite3", - connection: { - filename: dbFile, - }, -}); +Run the main app. Does you console log `Hello from Snippets` on ? -export default knexInstance; -``` +## End file structure At this point verify that your project structure looks like this: - api - snippets.js - app.js -- database.js - package.json diff --git a/courses/backend/node/week1/session-materials/04-database-connection.md b/courses/backend/node/week1/session-materials/04-database-connection.md new file mode 100644 index 00000000..6f2ae6c0 --- /dev/null +++ b/courses/backend/node/week1/session-materials/04-database-connection.md @@ -0,0 +1,52 @@ +# Database connection + +Let's create the database connection in a separate file: + +```js +// Contents of database.js + +import knex from "knex"; + +const dbFile = "PATH_TO_YOUR_SQLITE_DB"; + +const knexInstance = knex({ + client: "sqlite3", + connection: { + filename: dbFile, + }, +}); + +export default knexInstance; +``` + +For Express to handle JSON requests, we need to add `app.use(express.json())` to `app.js`: + +```js +// Contents of app.js + +import express from "express"; +const app = express(); +const port = process.env.PORT || 3000; + +app.use(express.json()); + +app.get("/", (req, res) => { + res.send("Hello Class!"); +}); + +// Rest of the file... +``` + +## Test the app + +Run the main app. Can you access the database from your endpoint? To make sure, add a simple `knex` request in the endpoint. + +## End file structure + +At this point verify that your project structure looks like this: + +- api + - snippets.js +- app.js +- database.js +- package.json diff --git a/courses/backend/node/week1/session-materials/05-get-endpoints.md b/courses/backend/node/week1/session-materials/05-get-endpoints.md index abfdd0ea..70d6f125 100644 --- a/courses/backend/node/week1/session-materials/05-get-endpoints.md +++ b/courses/backend/node/week1/session-materials/05-get-endpoints.md @@ -1,4 +1,4 @@ -# API +# GET endpoints ## `GET /api/snippets` diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index 1e1cf36f..b7ec37c0 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -1,42 +1,43 @@ # Session plan -- Focus on having lots of in class exercises. -- DON'T teach everything, let the students investigate topics on their own as well! -- Focus on how to read documentation, google answers and google errors!! -- Teach towards the students being able to solve the homework. - ## Session Outline - Express - - What is Express + - What is Express (10 mins) - [Live coding: setup a server](./session-materials/01-server.md) - - Routing in Express + - [Excercise: create a local project and database schema](./session-materials/02-schema.md) + - Routing in Express (20 mins) - `app.use` - `app.get` - [Live coding: routing](#appget-vs-appuse) - - [Excercise: Setup routing](./session-materials/03-api.md) - - URL parameters in Express + - [Excercise: Setup routing](./session-materials/03-routing.md) (10 mins) + - URL parameters in Express (30 mins) - [Explanation and live coding](#query-parameters-vs-url-parameters) + - [Excercise: connect to the database](./session-materials/04-database-connection.md) - [Excercise: GET endpoints](./session-materials/05-get-endpoints.md) - - Route order + - Route order (15 mins) - [Live coding: why route order matters](#route-order) - Logging and debugging - - Middleware + - Middleware (15 mins) - [`next` method](https://expressjs.com/en/guide/using-middleware.html) - Modifying `request` and `response` - - [Live coding: basic middleware example](#middleware) -- Postman + - Authentication (30 mins) + - Authentication explanation + - [Excercise: implement authentication](./session-materials/06-auth.md) +- Postman (30 mins) - Create collection and save queries - Sending `GET` requests - Query parameters in Postman - URL parameters in Postman + - Excercise: Test and save your queries ## Exercises 1. [Server](./session-materials/01-server.md): Setup project -1. [DB schema](./session-materials/02-schema.md): Setup MySQL database schema -1. [API](./session-materials/03-api.md): Snippets API exercises +2. [DB schema](./session-materials/02-schema.md): Setup MySQL database schema +3. [API](./session-materials/03-api.md): Snippets API exercises - [POST endpoint](./session-materials/04-post-endpoint.md) - [GET endpoints](./session-materials/05-get-endpoints.md) - [Authentication](./session-materials/06-auth.md) diff --git a/courses/backend/node/week1/session-materials/04-post-endpoint.md b/courses/backend/node/week2/session-materials/04-post-endpoint.md similarity index 68% rename from courses/backend/node/week1/session-materials/04-post-endpoint.md rename to courses/backend/node/week2/session-materials/04-post-endpoint.md index 2ef68c65..a30b90b6 100644 --- a/courses/backend/node/week1/session-materials/04-post-endpoint.md +++ b/courses/backend/node/week2/session-materials/04-post-endpoint.md @@ -42,34 +42,6 @@ POST /api/snippets } ``` -But first, for Express to handle JSON requests, we need to add `app.use(express.json())` to `app.js`: - -```js -// Contents of app.js - -import express from "express"; -const app = express(); -const port = process.env.PORT || 3000; - -app.use(express.json()); - -app.get("/", (req, res) => { - res.send("Hello Class!"); -}); - -// Rest of the file... -``` - ---- - -One remaining thing in the setup is to actually use the router we're exporting from `api/snippets.js`. -Inside `app.js`, below `app.use(express.json())`, add the following: - -```js -import snippetsRouter from "./api/snippets.js"; -app.use("/api/snippets", snippetsRouter); -``` - --- **Task:** when we now make a request like From 6cca8f823f224d8b71614a181cf0864576ae2a67 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 8 Oct 2025 20:35:20 +0200 Subject: [PATCH 15/21] chore: remove items from readme, refresh prep links --- courses/backend/node/week1/README.md | 60 ----------------------- courses/backend/node/week1/preparation.md | 8 +-- 2 files changed, 4 insertions(+), 64 deletions(-) diff --git a/courses/backend/node/week1/README.md b/courses/backend/node/week1/README.md index 8ea880f5..8788d4ae 100644 --- a/courses/backend/node/week1/README.md +++ b/courses/backend/node/week1/README.md @@ -17,63 +17,3 @@ By the end of this session, you will be able to: - [ ] Use logging and debugging tools to monitor and troubleshoot Node.js applications. - [ ] Apply middleware functions in Express to process requests and responses. - [ ] Use Postman to test and debug APIs you have built. - -TODO - Move this content somewhere else - -When writing an Express application we are registering routes on HTTP keywords (GET,POST,PUT, DELETE) and handler functions for those routes. - -The most basic express webserver looks like the following: - -```js -// We require the express package after having installed it -// via “npm i express” -import express from "express"; - -// We create an express instance and bind it to our app const -const app = express(); - -// We register the first route with the following; - -// all “GET” requests on the path “/“ -// will be handled by the function (req, res) {} -app.get("/", function (req, res) { - // the handler receives the request on “req” - // and has access to the response on “res” - - // res.send allows us to send a response to a request - res.send("hello world"); -}); - -// We start the express webserver by listening to port 3000 -app.listen(3000); -``` - -If we run this script, node will start up and run the code ending with app.listen(3000). This tells Node to bind and listen for connections on this specified host and port. - -If there is no specified host, Node will bind to localhost and the Node application will be available on . - -If you open that URL in the browser you will see “hello world” (without any styling). - -## Extensibility - -This is a very basic example of an express application. There many parameters to tweak and cases to take into consideration. -One of the first things we’d like to do in a typical Express application is to not return strings or HTML, but instead use JSON as the transport encoding format. Meaning we accept JSON in requests and we can respond with JSON formatted data. - -There are a few good extensions or middleware that is easy to plug into express and allows us to tweak and change some of the parts of the webserver. Some of these middleware extensions include body-parser, compression, cors, errorhandler - find the full list here: . - -### Relevant links - -- [Preparation](preparation.md) -- [Assignment](./assignment.md) -- [Session plan](./session-plan.md) - -### Express.js - -In Node.js it's possible to make a HTTP server using the native `http` module. However, this is rarely used in practice. Instead, we'll use [Express.js](https://expressjs.com/), a backend framework that can do what the `http` module does and much more (in a simpler, faster and more readable way). - -Practically speaking, what can we do with a web server like `http` or `Express`? All the magic that makes the frontend work: - -- Get and store data that comes from the frontend -- Make API calls to other services -- Secure data that comes from both the frontend and the database -- Any other type of calculation or business logic diff --git a/courses/backend/node/week1/preparation.md b/courses/backend/node/week1/preparation.md index 5d913023..120826d9 100644 --- a/courses/backend/node/week1/preparation.md +++ b/courses/backend/node/week1/preparation.md @@ -1,13 +1,13 @@ # Preparation +- Refreshment on [intro to backend from Foundation](../../../foundation/intro-to-backend/README.md) - (10 min) -- [Introduction to Express](https://youtu.be/9TSBKO59u0Y)> (8 min) -- [Express Route Params](https://youtu.be/MuMs1pLuT7I) (5 min) +- Setup your Node to the [latest stable version (LTS)](https://stackoverflow.com/questions/10075990/upgrading-node-js-to-the-latest-version) ## Optional Resources For more research, you can explore the following resources: -- [Express JS Crash Course](https://www.youtube.com/watch?v=L72fhGm1tfE) +- [Express Crash Course](https://www.youtube.com/watch?v=CnH3kAXSrmU) - [Going out to eat and understanding the basics of Express.js](https://medium.freecodecamp.org/going-out-to-eat-and-understanding-the-basics-of-express-js-f034a029fb66) -- [Express documentation](https://expressjs.com/en/4x/api.html) +- [Express documentation](https://expressjs.com/en/5x/api.html) From b71c7544ea41272891044a2703806ca50d6c4947 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Wed, 8 Oct 2025 20:38:35 +0200 Subject: [PATCH 16/21] chore: update links --- courses/backend/node/week1/session-plan.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index b7ec37c0..d0b4a66f 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -37,8 +37,8 @@ 1. [Server](./session-materials/01-server.md): Setup project 2. [DB schema](./session-materials/02-schema.md): Setup MySQL database schema -3. [API](./session-materials/03-api.md): Snippets API exercises - - [POST endpoint](./session-materials/04-post-endpoint.md) +3. [API](./session-materials/03-routing.md): Snippets API exercises + - [POST endpoint](./session-materials/03-routing.md) - [GET endpoints](./session-materials/05-get-endpoints.md) - [Authentication](./session-materials/06-auth.md) From bbec7d4f05acac97eda88516b235a73f905346b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Magdalena=20Odrow=C4=85=C5=BC-=C5=BBelezik?= Date: Mon, 27 Oct 2025 18:39:57 +0100 Subject: [PATCH 17/21] Apply suggestions from code review Co-authored-by: Adam Blanchard --- courses/backend/node/module-materials/examples/route-order.js | 2 +- courses/backend/node/week1/README.md | 2 +- courses/backend/node/week1/preparation.md | 2 +- courses/backend/node/week1/session-materials/01-server.md | 3 +-- 4 files changed, 4 insertions(+), 5 deletions(-) diff --git a/courses/backend/node/module-materials/examples/route-order.js b/courses/backend/node/module-materials/examples/route-order.js index 0c0b92ad..7ae1e09b 100644 --- a/courses/backend/node/module-materials/examples/route-order.js +++ b/courses/backend/node/module-materials/examples/route-order.js @@ -9,7 +9,7 @@ app.get("/data", (_req, res) => { res.send({ data: 2 }); }); -////// which one is being called when? +// Which one is being called when? app.get("/data/:id", (req, res) => { console.log("Parametrized URL"); diff --git a/courses/backend/node/week1/README.md b/courses/backend/node/week1/README.md index 8788d4ae..457c425d 100644 --- a/courses/backend/node/week1/README.md +++ b/courses/backend/node/week1/README.md @@ -1,4 +1,4 @@ -# Session plan (Week 1) +# Express (Week 1) In this session we will focus on Express.js, which is an application framework for building webservers in an easy manner. Although we could write everything from scratch in Node, we don’t want to. In many situations a webserver is generic enough for us and we gain a lot by using something that just works and makes our lives as developers easier – as long as what we are trying to do is within the scope of Express. diff --git a/courses/backend/node/week1/preparation.md b/courses/backend/node/week1/preparation.md index 120826d9..0749f8d6 100644 --- a/courses/backend/node/week1/preparation.md +++ b/courses/backend/node/week1/preparation.md @@ -1,6 +1,6 @@ # Preparation -- Refreshment on [intro to backend from Foundation](../../../foundation/intro-to-backend/README.md) +- Refreshment on [intro to backend from Foundation](/courses/foundation/intro-to-backend/README.md) - (10 min) - Setup your Node to the [latest stable version (LTS)](https://stackoverflow.com/questions/10075990/upgrading-node-js-to-the-latest-version) diff --git a/courses/backend/node/week1/session-materials/01-server.md b/courses/backend/node/week1/session-materials/01-server.md index 317e86ae..d83c59d2 100644 --- a/courses/backend/node/week1/session-materials/01-server.md +++ b/courses/backend/node/week1/session-materials/01-server.md @@ -1,13 +1,12 @@ # Server -Create a new, separate folder somewhere on your machine: +Create a new directory inside "node" in your `hyf-assignment` repo: ```shell mkdir nodejs-week1 code nodejs-week1 # to open the folder in VS Code ``` -You can also reuse some other folder. Main thing to watch out for is that the folder you decide to use should ideally be empty. --- From c71f82269c7270493be75ac34bc644852ad61b95 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 27 Oct 2025 19:10:01 +0100 Subject: [PATCH 18/21] chore: updated the assignment to remove meal sharing --- courses/backend/node/week1/assignment.md | 209 +++++++++++------------ 1 file changed, 95 insertions(+), 114 deletions(-) diff --git a/courses/backend/node/week1/assignment.md b/courses/backend/node/week1/assignment.md index 0d534f90..6fa5b558 100644 --- a/courses/backend/node/week1/assignment.md +++ b/courses/backend/node/week1/assignment.md @@ -1,41 +1,12 @@ # Assignment -This homework, just like the previous week, will result in 2 pull requests: +As usual, create a PR in your HYF assignments repository. -- A pull request for the **Warmup** - in your regular hyf-homework repository -- A pull request for the additional **Meal sharing endpoints** - in the meal-sharing repository +In the repository, create a `nodejs-week1` branch from `main` to work on the homework (`git checkout -b nodejs-week1` ) -In both repositories, create a `nodejs-week1` branch from `main` to work on the homework (`git checkout -b nodejs-week1` ) +## Setup -## Warmup - -For the warmup you're going to build a search engine. The search engine will have 3 routes: - -- `GET /search` -- `GET /documents/:id` -- `POST /search` - -The purpose of the search engine is to search and find documents from a file called `documents.json`. Example contents: - -```json -[ - { - "id": 1, - "name": "Used Apple Airpods", - "price": "50", - "description": "Battery life is not great" - }, - { - "id": 2, - "type": "doc", - "value": "hello world" - } -] -``` - -### Setup - -Go to `nodejs/week1` in your `hyf-homework` repo: +Go to `nodejs/week1` in your `hyf-assignment` repo: ```shell npm init -y @@ -69,7 +40,87 @@ app.listen(port, () => { }); ``` -You also need to create a `documents.json` file. +## Part 1: Add more snippets and tags + +You will continue working with the schema used in the session. This week you will build more endpoints, developing some [CRUD operations](https://www.freecodecamp.org/news/crud-operations-explained/) to be able to add more data via your backend. + +### Excercise goal + +We have users and users post snippets. We need to be able to post those snippets to the database and also edit or delete the snippets via backend endpoints. + +We also need some categorisation mechanics. Let's add a **tags** table, that will store tags available to be added to our snippets, so it is easier to search them. Each snippet can have many tags and each tag can belong to many snippets. + +_Note: you need to modify the snippets column in the database on top of adding new tables to achieve this._ + +### Routes + +For this week's assignment, we will have two categories of routes: snippets and tags. + +- The routes for snippets will go into `/api/src/routers/snippets.js` +- The tags routes will live in `/api/src/routers/tags.js` + +This means that we will end up having two Routers: a snippets and tags router. +You can [read more about Express Routers](https://expressjs.com/en/guide/routing.html). + +#### Snippets + +| Route | HTTP method | Description | +| ------------------- | ----------- | ---------------------------------- | +| `/api/snippets` | GET | Returns all snippets | +| `/api/snippets` | POST | Adds a new snippet to the database | +| `/api/snippets/:id` | GET | Returns the snippet by `id` | +| `/api/snippets/:id` | PUT | Updates the snippet by `id` | +| `/api/snippets/:id` | DELETE | Deletes the snippet by `id` | + +#### Tags + +Now that you have built the basic set of endpoints for **snippets**, you can get some more practice and expand your app backend by creating the same for **tags**: + +| Route | HTTP method | Description | +| --------------- | ----------- | ------------------------------ | +| `/api/tags` | GET | Returns all tags | +| `/api/tags` | POST | Adds a new tag to the database | +| `/api/tags/:id` | GET | Returns a tag by `id` | +| `/api/tags/:id` | PUT | Updates the tag by `id` | +| `/api/tags/:id` | DELETE | Deletes the tag by `id` | + +##### Requests + +All the `POST` or `PUT` endpoints will require a request body - the information that your database will be updated with. + +##### Responses + +All the specified `GET` routes should respond with JSON with the available columns from the associated tables. + +The `GET`, `PUT` and `DELETE` routes that include an `/:id` in the path should make sure to handle the case when the row with that ID does not exist. + +> Think about what special HTTP status code would be appropriate for that scenario. + +You are free to decide on the response for a successful `POST`, `PUT` and `DELETE` request. Some ideas: + +- Respond with an acknowledgement message: `{ "message": "Deleted snippet" }` +- Respond with data from the row itself like with `GET` + +And lastly, if the `POST` request is successful, the response status code should be 201 Created, as that would indicate something was _created_. + +#### Knex + +Your usage of Knex should be getting a bit more advanced now. You will move from `knex.raw` on to different `knex` function, for example: + +- `.select`, `.from`, `.where` +- `.insert` +- `.update` +- `.del` (for deletion) + +Check out the [Knex cheatsheet](https://devhints.io/knex)! + +## Part 2: search engine + +For the excercise you're going to build a search engine. The search engine will have 3 routes: + +- `GET /search` +- `GET /snippets/:id` +- `POST /search` ### `GET /search` @@ -78,22 +129,22 @@ This endpoint will accept a query parameter called `q`, short for _query_. A bit - If `q` is not provided, the endpoint should return all documents. - If `q` is provided, the endpoint should return the documents with some field that matches the value of `q`. -Example response if we call `GET /search?q=hello`: +Example response if we call `GET /search?q=react`: ```json [ { "id": 2, - "type": "doc", - "value": "hello world" + "title": "React Hooks", + "contents": "Explaining useState and useEffect with examples." } ] ``` -### `GET /documents/:id` +### `GET /snippets/:id` -This endpoint is simple: find and respond with the document matching the `id` parameter. If there is no such document, respond with a 404 Not Found. -You can assume that the document IDs are unique so there's no need to handle duplicates. +This endpoint is simple: find and respond with the snippet matching the `id` parameter. If there is no such snippet, respond with a 404 Not Found. +You can assume that the snippet IDs are unique so there's no need to handle duplicates. ### `POST /search` @@ -108,7 +159,7 @@ Example request: POST /search { "fields": { - "price": "50" + "tags": "React" } } ``` @@ -119,81 +170,11 @@ Response to the example request: [ { "id": 1, - "name": "Used Apple Airpods", - "price": "50", - "description": "Battery life is not great" + "title": "React Hooks", + "contents": "Explaining useState and useEffect with examples.", + "tags": ["React"] } ] ``` If both `q` (query parameter) and `fields` (in body) are provided, we should respond with status 400 Bad Request and explain that both can't be provided. - -## Meal sharing endpoints - -You will continue working in the meal-sharing repository for this homework. This week you will build more endpoints, developing some [CRUD operations](https://www.freecodecamp.org/news/crud-operations-explained/) for your future meal sharing website backend functionality. - -### Routes - -In last week's homework you added routes in `/api/src/index.js`. You can just leave them there as they are. - -For this week's homework, we will add two categories of routes: meals and reservations. - -- The routes for meals will go into `/api/src/routers/meals.js` -- The reservation routes will live in `/api/src/routers/reservations.js` - -This means that we will end up having two Routers: a meals router and a reservations router. -You can [read more about Express Routers](https://expressjs.com/en/guide/routing.html). - -You can reference the file `/api/src/routers/nested.js` for an example, and see how it is used in `/api/src/index.js`. - -#### Meals - -| Route | HTTP method | Description | -| ---------------- | ----------- | ------------------------------- | -| `/api/meals` | GET | Returns all meals | -| `/api/meals` | POST | Adds a new meal to the database | -| `/api/meals/:id` | GET | Returns the meal by `id` | -| `/api/meals/:id` | PUT | Updates the meal by `id` | -| `/api/meals/:id` | DELETE | Deletes the meal by `id` | - -#### Reservations - -Now that you have built the basic set of endpoints for **meals**, you can get some more practice and expand your app backend by creating the same for **reservations**: - -| Route | HTTP method | Description | -| ----------------------- | ----------- | -------------------------------------- | -| `/api/reservations` | GET | Returns all reservations | -| `/api/reservations` | POST | Adds a new reservation to the database | -| `/api/reservations/:id` | GET | Returns a reservation by `id` | -| `/api/reservations/:id` | PUT | Updates the reservation by `id` | -| `/api/reservations/:id` | DELETE | Deletes the reservation by `id` | - -##### Requests - -All the `POST` or `PUT` endpoints will require a request body - the information that your database will be updated with. - -##### Responses - -All the specified `GET` routes should respond with JSON with the available columns from the associated tables. - -The `GET`, `PUT` and `DELETE` routes that include an `/:id` in the path should make sure to handle the case when the row with that ID does not exist. - -> Think about what special HTTP status code would be appropriate for that scenario. - -You are free to decide on the response for a successful `POST`, `PUT` and `DELETE` request. Some ideas: - -- Respond with an acknowledgement message: `{ "message": "Deleted meal" }` -- Respond with data from the row itself like with `GET` - -And lastly, if the `POST` request is successful, the response status code should be 201 Created, as that would indicate something was _created_. - -#### Knex - -Your usage of Knex should be getting a bit more advanced now. You will move from `knex.raw` on to different `knex` function, for example: - -- `.select`, `.from`, `.where` -- `.insert` -- `.update` -- `.del` (for deletion) - -Check out the [Knex cheatsheet](https://devhints.io/knex)! From a518901a42b7869bd93310d2f37e2a50d436fd10 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 27 Oct 2025 19:26:31 +0100 Subject: [PATCH 19/21] chore: add more on authentication --- courses/backend/node/week1/session-plan.md | 112 +++++++++++++++++++-- 1 file changed, 105 insertions(+), 7 deletions(-) diff --git a/courses/backend/node/week1/session-plan.md b/courses/backend/node/week1/session-plan.md index d0b4a66f..6b2f1946 100644 --- a/courses/backend/node/week1/session-plan.md +++ b/courses/backend/node/week1/session-plan.md @@ -24,14 +24,8 @@ - - [Live coding: basic middleware example](#middleware) - Authentication (30 mins) - - Authentication explanation + - [Authentication explanation](#authentication-explanation) - [Excercise: implement authentication](./session-materials/06-auth.md) -- Postman (30 mins) - - Create collection and save queries - - Sending `GET` requests - - Query parameters in Postman - - URL parameters in Postman - - Excercise: Test and save your queries ## Exercises @@ -97,3 +91,107 @@ Code example of a sample middleware can be found in the [middleware file](../mod ```js node --watch examples/middleware.js ``` + +### Authentication explanation + +Think of authentication like different ways to prove who you are when visiting a building. You need to show ID, but how you do it matters for security and convenience. + +#### Database-Stored Tokens (What We're Using) + +Server issues a unique token on login and stores it in the database to validate against on each request. + +**Metaphor:** Like a membership card the building keeps a copy of in their files. + +**Pros:** + +- Simple to implement and understand +- Easy to revoke access by deleting the token from database +- Full control over token lifecycle + +**Cons:** + +- Requires database lookup on every authenticated request (slower) +- Database becomes a bottleneck at scale +- If database is slow or down, authentication fails + +**Security:** 🟡🟡🟡⚪⚪ (3/5) - Secure, but tokens must be properly generated and stored safely. + +#### JWT (JSON Web Tokens) + +A self-contained token with user info encoded inside, signed by the server and verifiable without database lookups. + +**Metaphor:** Like a driver's license that the DMV signs with a special stamp - you can't fake it because only they have the stamp. + +**Pros:** + +- No database lookups needed (much faster) +- Works across multiple servers in distributed systems +- Contains user information directly in the token + +**Cons:** + +- Cannot easily revoke tokens until they expire +- If secret key is stolen, attackers can create fake tokens +- Tokens cannot be modified once issued + +**Security:** 🟢🟢🟢🟢⚪ (4/5) - Very secure when implemented correctly with proper secret key management. + +#### Session-Based Authentication + +Server creates a session ID on login and stores session data in server memory or cache, sending only the ID to the client. + +**Metaphor:** Like getting a visitor badge when you arrive that you wear the entire visit, while the building keeps track of all active badges in a secure room. + +**Pros:** + +- Can instantly revoke access by deleting session +- Server maintains full control over authentication state +- Simple logout process + +**Cons:** + +- Requires server to store session data (memory/cache) +- Does not work well with multiple servers without shared storage +- Session storage must scale with user load + +**Security:** 🟢🟢🟢🟢⚪ (4/5) - Secure for traditional web applications with proper session management. + +#### API Keys + +A single permanent credential issued once and used for all requests, typically for machine-to-machine communication. + +**Metaphor:** Like having a permanent membership card that never expires. + +**Pros:** + +- Extremely simple to implement +- Easy for developers to use in their code +- No complex authentication flow needed + +**Cons:** + +- If stolen, access continues until key is regenerated +- No built-in expiration +- Lacks granular permission controls + +**Security:** 🟡🟡⚪⚪⚪ (2/5) - Moderately secure for machine-to-machine communication, weak for user authentication. + +#### Basic Authentication (Username/Password) + +Client sends username and password directly in the request headers with every API call. + +**Metaphor:** Like showing your ID card at the entrance every single time you visit - literally showing your credentials each visit. + +**Pros:** + +- Simplest to implement +- Works everywhere, universal standard +- Easy to understand + +**Cons:** + +- Credentials sent with every request (security risk) +- Cannot revoke access without changing password +- Highly insecure over unencrypted connections + +**Security:** 🔴⚪⚪⚪⚪ (1/5) - Very insecure, only acceptable over HTTPS and for internal tooling. From 765b8d657e98b14f5b77c62f90ea061058851753 Mon Sep 17 00:00:00 2001 From: magdazelena Date: Mon, 27 Oct 2025 19:30:37 +0100 Subject: [PATCH 20/21] chore: fix linting --- courses/backend/node/week1/session-materials/01-server.md | 1 - 1 file changed, 1 deletion(-) diff --git a/courses/backend/node/week1/session-materials/01-server.md b/courses/backend/node/week1/session-materials/01-server.md index d83c59d2..0eaf16fc 100644 --- a/courses/backend/node/week1/session-materials/01-server.md +++ b/courses/backend/node/week1/session-materials/01-server.md @@ -7,7 +7,6 @@ mkdir nodejs-week1 code nodejs-week1 # to open the folder in VS Code ``` - --- Initialize and install: From 4468cea9df2fb1c177a807782ed57560be94e88c Mon Sep 17 00:00:00 2001 From: Adam Blanchard Date: Tue, 28 Oct 2025 10:01:39 +0100 Subject: [PATCH 21/21] Update courses/backend/node/week1/assignment.md --- courses/backend/node/week1/assignment.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/courses/backend/node/week1/assignment.md b/courses/backend/node/week1/assignment.md index 6fa5b558..6547b98e 100644 --- a/courses/backend/node/week1/assignment.md +++ b/courses/backend/node/week1/assignment.md @@ -2,7 +2,7 @@ As usual, create a PR in your HYF assignments repository. -In the repository, create a `nodejs-week1` branch from `main` to work on the homework (`git checkout -b nodejs-week1` ) +In the repository, create a `nodejs-week1` branch from `main` to work on the assignment (`git checkout -b nodejs-week1` ) ## Setup