|
1 | | -# Assignment |
2 | | - |
3 | | -Once again, you will deliver 2 pull requests: |
4 | | - |
5 | | -- A pull request for the **Warmup** - in your regular hyf-homework repository |
6 | | -- A pull request for the additional **meal sharing endpoints** - in the meal-sharing repository |
7 | | - |
8 | | -In both repositories, create a `nodejs-week2` branch from `main` to work on the homework (`git checkout -b nodejs-week2` ). |
9 | | - |
10 | | -## Warmup |
11 | | - |
12 | | -For the warmup you will be handed a Contacts API with a single endpoint: |
13 | | - |
14 | | -- `GET /api/contacts` |
15 | | - |
16 | | -This endpoint accepts a query parameter `sort`. Here's how you can use it: |
17 | | - |
18 | | -- `GET /api/contacts?sort=first_name%20ASC` |
19 | | - - Sorts contacts by first name, ascending |
20 | | -- `GET /api/contacts?sort=last_name%20DESC` |
21 | | - - Sorts contacts by last name, descending |
22 | | - |
23 | | -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. |
24 | | - |
25 | | -### Setup |
26 | | - |
27 | | -TODO - Review assignment to work with sqlite. |
28 | | - |
29 | | -Go to `nodejs/week2` in your `hyf-homework` repo: |
30 | | - |
31 | | -```shell |
32 | | -npm init -y |
33 | | -npm i express sqlite3 knex |
34 | | -npm set-script dev "node --watch app.js" |
35 | | -``` |
36 | | - |
37 | | -Make sure you have `"type": "module"` in your `package.json`. |
38 | | - |
39 | | -You should also ensure that the `node_modules/` folder is ignored by Git: |
40 | | - |
41 | | -```shell |
42 | | -echo node_modules/ >> .gitignore |
43 | | -``` |
44 | | - |
45 | | -Create a database/schema called `hyf_node_week2_warmup` with a `contacts` table: |
46 | | - |
47 | | -```sql |
48 | | -CREATE TABLE `contacts` ( |
49 | | - `id` int unsigned NOT NULL AUTO_INCREMENT, |
50 | | - `first_name` varchar(255) NOT NULL, |
51 | | - `last_name` varchar(255) NOT NULL, |
52 | | - `email` varchar(255) DEFAULT NULL, |
53 | | - `phone` varchar(255) DEFAULT NULL, |
54 | | - PRIMARY KEY (`id`) |
55 | | -) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4; |
56 | | - |
57 | | --- Sample data |
58 | | -insert into contacts (id, first_name, last_name, email, phone) values (1, 'Selig', 'Matussov', 'smatussov0@pinterest.com', '176-630-4577'); |
59 | | -insert into contacts (id, first_name, last_name, email, phone) values (2, 'Kenny', 'Yerrington', null, null); |
60 | | -insert into contacts (id, first_name, last_name, email, phone) values (3, 'Emilie', 'Gaitskell', null, null); |
61 | | -insert into contacts (id, first_name, last_name, email, phone) values (4, 'Jordon', 'Tokell', null, null); |
62 | | -insert into contacts (id, first_name, last_name, email, phone) values (5, 'Sallyann', 'Persse', 'spersse4@webnode.com', '219-157-2368'); |
63 | | -insert into contacts (id, first_name, last_name, email, phone) values (6, 'Berri', 'Bulter', null, null); |
64 | | -insert into contacts (id, first_name, last_name, email, phone) values (7, 'Lanni', 'Ivanilov', 'livanilov6@fda.gov', null); |
65 | | -insert into contacts (id, first_name, last_name, email, phone) values (8, 'Dagny', 'Milnthorpe', null, null); |
66 | | -insert into contacts (id, first_name, last_name, email, phone) values (9, 'Annadiane', 'Bansal', null, null); |
67 | | -insert into contacts (id, first_name, last_name, email, phone) values (10, 'Tawsha', 'Hackley', null, null); |
68 | | -insert into contacts (id, first_name, last_name, email, phone) values (11, 'Rubetta', 'Ozelton', null, null); |
69 | | -insert into contacts (id, first_name, last_name, email, phone) values (12, 'Charles', 'Boughey', 'cbougheyb@senate.gov', '605-358-5664'); |
70 | | -insert into contacts (id, first_name, last_name, email, phone) values (13, 'Shantee', 'Robbe', null, null); |
71 | | -insert into contacts (id, first_name, last_name, email, phone) values (14, 'Gleda', 'Peat', null, null); |
72 | | -insert into contacts (id, first_name, last_name, email, phone) values (15, 'Arlinda', 'Ethersey', 'aetherseye@biglobe.ne.jp', '916-139-1300'); |
73 | | -insert into contacts (id, first_name, last_name, email, phone) values (16, 'Armando', 'Meachem', 'ameachemf@oaic.gov.au', '631-442-5339'); |
74 | | -insert into contacts (id, first_name, last_name, email, phone) values (17, 'Codi', 'Redhouse', null, '401-953-6897'); |
75 | | -insert into contacts (id, first_name, last_name, email, phone) values (18, 'Ann', 'Buncombe', 'abuncombeh@ow.ly', '210-338-0748'); |
76 | | -insert into contacts (id, first_name, last_name, email, phone) values (19, 'Louis', 'Matzkaitis', 'lmatzkaitisi@ebay.com', '583-996-6979'); |
77 | | -insert into contacts (id, first_name, last_name, email, phone) values (20, 'Jessey', 'Pala', null, null); |
78 | | -insert into contacts (id, first_name, last_name, email, phone) values (21, 'Archy', 'Scipsey', 'ascipseyk@ask.com', '420-983-2426'); |
79 | | -insert into contacts (id, first_name, last_name, email, phone) values (22, 'Benoit', 'Mould', 'bmouldl@bing.com', '271-217-9218'); |
80 | | -insert into contacts (id, first_name, last_name, email, phone) values (23, 'Sherm', 'Girardey', 'sgirardeym@guardian.co.uk', '916-999-2957'); |
81 | | -insert into contacts (id, first_name, last_name, email, phone) values (24, 'Raquel', 'Mudge', 'rmudgen@slate.com', '789-830-7473'); |
82 | | -insert into contacts (id, first_name, last_name, email, phone) values (25, 'Tabor', 'Reavey', null, null); |
83 | | -``` |
84 | | - |
85 | | -Create `app.js`: |
86 | | - |
87 | | -```js |
88 | | -import knex from "knex"; |
89 | | -import express from "express"; |
90 | | - |
91 | | -const dbFile = "PATH_TO_YOUR_SQLITE_DB"; |
92 | | - |
93 | | -const knexInstance = knex({ |
94 | | - client: "sqlite3", |
95 | | - connection: { |
96 | | - filename: dbFile, |
97 | | - }, |
98 | | -}); |
99 | | - |
100 | | -const app = express(); |
101 | | -const port = process.env.PORT || 3000; |
102 | | - |
103 | | -app.use(express.json()); |
104 | | - |
105 | | -const apiRouter = express.Router(); |
106 | | -app.use("/api", apiRouter); |
107 | | - |
108 | | -const contactsAPIRouter = express.Router(); |
109 | | -apiRouter.use("/contacts", contactsAPIRouter); |
110 | | - |
111 | | -contactsAPIRouter.get("/", async (req, res) => { |
112 | | - let query = knexInstance.select("*").from("contacts"); |
113 | | - |
114 | | - if ("sort" in req.query) { |
115 | | - const orderBy = req.query.sort.toString(); |
116 | | - if (orderBy.length > 0) { |
117 | | - query = query.orderByRaw(orderBy); |
118 | | - } |
119 | | - } |
120 | | - |
121 | | - console.log("SQL", query.toSQL().sql); |
122 | | - |
123 | | - try { |
124 | | - const data = await query; |
125 | | - res.json({ data }); |
126 | | - } catch (e) { |
127 | | - console.error(e); |
128 | | - res.status(500).json({ error: "Internal server error" }); |
129 | | - } |
130 | | -}); |
131 | | - |
132 | | -app.listen(port, () => { |
133 | | - console.log(`Listening on port ${port}`); |
134 | | -}); |
135 | | -``` |
136 | | - |
137 | | -As mentioned above, the `sort` query parameter has been introduced with a SQL injection vulnerability. |
138 | | - |
139 | | -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. |
140 | | -You can for instance demonstrate this with a screen recording and include it in the PR description. |
141 | | - |
142 | | -After having demonstrated the SQL injection vulnerability, the goal is then to fix the issue by updating `app.js`. |
143 | | - |
144 | | -**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. |
145 | | - |
146 | | -## Meal sharing endpoints |
147 | | - |
148 | | -You will continue working in the meal-sharing repository for this task. |
149 | | - |
150 | | -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. |
151 | | - |
152 | | -### Routes |
153 | | - |
154 | | -#### Meals |
155 | | - |
156 | | -Work with your `GET api/meals` route to add the query parameters. |
157 | | -Make sure that the query parameters can be combined, f.x. <nobr>`?limit=4&maxPrice=90`.<nobr/> |
158 | | - |
159 | | -| Parameter | Data type | Description | Example | |
160 | | -| ----------------------- | --------- | --------------------------------------------------------------------------------------------------------------------------------------- | --------------------------------------------------- | |
161 | | -| `maxPrice` | Number | Returns all meals that are cheaper than `maxPrice`. | <nobr>`api/meals?maxPrice=90`<nobr/> | |
162 | | -| `availableReservations` | Boolean | Returns all meals that still have available spots left, if `true`. If `false`, return meals that have no available spots left.[^1] | <nobr>`api/meals?availableReservations=true`<nobr/> | |
163 | | -| `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`. | <nobr>`api/meals?title=Indian%20platter`<nobr/> | |
164 | | -| `dateAfter` | Date | Returns all meals where the date for `when` is after the given date. | `api/meals?dateAfter=2022-10-01` | |
165 | | -| `dateBefore` | Date | Returns all meals where the date for `when` is before the given date. | `api/meals?dateBefore=2022-08-08` | |
166 | | -| `limit` | Number | Returns the given number of meals. | `api/meals?limit=7` | |
167 | | -| `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` | |
168 | | -| `sortDir`[^3] | String | Returns all meals sorted in the given direction. Only works combined with the `sortKey` and allows `asc` or `desc`. | <nobr>`api/meals?sortKey=price&sortDir=desc`<nobr/> | |
169 | | - |
170 | | -[^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`. |
171 | | - |
172 | | -[^2]: This used to be `sort_key` in a previous version of the homework text. |
173 | | - |
174 | | -[^3]: This used to be `sort_dir` in a previous version of the homework text. |
175 | | - |
176 | | -#### Reviews |
177 | | - |
178 | | -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**: |
179 | | - |
180 | | -| Route | HTTP method | Description | |
181 | | -| ----------------------------- | ----------- | ---------------------------------------- | |
182 | | -| `/api/reviews` | GET | Returns all reviews. | |
183 | | -| `/api/meals/:meal_id/reviews` | GET | Returns all reviews for a specific meal. | |
184 | | -| `/api/reviews` | POST | Adds a new review to the database. | |
185 | | -| `/api/reviews/:id` | GET | Returns a review by `id`. | |
186 | | -| `/api/reviews/:id` | PUT | Updates the review by `id`. | |
187 | | -| `/api/reviews/:id` | DELETE | Deletes the review by `id`. | |
188 | | - |
189 | | -#### Knex |
190 | | - |
191 | | -You should try to avoid using `knex.raw` and instead use the different `knex` functions, for example: |
192 | | - |
193 | | -- `.select`, `.from`, `.where`, `join`, `leftJoin` |
194 | | -- `.insert` |
195 | | -- `.update` |
196 | | -- `.del` (for deletion) |
197 | | - |
198 | | -Check out the [Knex cheatsheet](https://devhints.io/knex)! |
199 | | - |
200 | | -## Hand in homework |
201 | | - |
202 | | -Need to brush up on the homework hand-in process? |
203 | | - |
204 | | -Check [this resource](https://github.com/HackYourFuture-CPH/Git/blob/main/homework-submission.md) to remember how to hand in the homework correctly! |
205 | | - |
206 | | -## Feedback |
207 | | - |
208 | | -And finally, please take two minutes to answer the survey [here](https://forms.gle/YG5KCnSCPhb8dJAL9) to give feedback to the staff and mentors. |
0 commit comments