├── .gitignore ├── README.md ├── assessments ├── project-descriptor.pdf └── project-marking-rubric.pdf ├── course-directive.pdf ├── lecture-notes ├── week-01-formative-assessment.js ├── week-01-github-javascript.md ├── week-02-rest-express-http.md ├── week-03-postgresql-docker-jsdoc-swagger.md ├── week-04-api-versioning-relationships-repository-pattern.md ├── week-05-validation-filtering-sorting.md ├── week-06-seeding-api-testing-render-deployment.md ├── week-07-logging-authentication-jail.md ├── week-08-role-based-access-control.md ├── week-09-rate-limiting-securing-http-headers.md └── week-10-erd-documentation-software-methodologies.md └── resources (ignore) ├── img ├── 03 │ ├── render-1.PNG │ ├── render-10.PNG │ ├── render-11.png │ ├── render-12.png │ ├── render-13.png │ ├── render-14.png │ ├── render-15.png │ ├── render-2.PNG │ ├── render-3.PNG │ ├── render-4.PNG │ ├── render-5.PNG │ ├── render-6.PNG │ ├── render-7.PNG │ ├── render-8.PNG │ ├── render-9.PNG │ ├── swagger-1.png │ ├── swagger-10.png │ ├── swagger-11.png │ ├── swagger-12.png │ ├── swagger-13.png │ ├── swagger-14.png │ ├── swagger-15.png │ ├── swagger-16.png │ ├── swagger-2.png │ ├── swagger-3.png │ ├── swagger-4.png │ ├── swagger-5.png │ ├── swagger-6.png │ ├── swagger-7.png │ ├── swagger-8.png │ └── swagger-9.png ├── 04 │ ├── swagger-1.png │ ├── swagger-2.png │ ├── swagger-3.png │ ├── swagger-4.png │ ├── swagger-5.png │ ├── swagger-6.png │ └── swagger-7.png ├── 05 │ ├── swagger-1.PNG │ ├── swagger-2.PNG │ ├── swagger-3.PNG │ ├── swagger-4.PNG │ ├── swagger-5.PNG │ ├── swagger-6.PNG │ └── swagger-7.PNG ├── 06 │ ├── capture-1.PNG │ ├── capture-2.PNG │ ├── capture-3.PNG │ ├── capture-4.PNG │ ├── capture-5.PNG │ └── capture-6.PNG ├── 07 │ ├── capture-1.PNG │ ├── capture-2.PNG │ ├── capture-3.PNG │ └── capture-4.PNG ├── 09 │ ├── helmet-1.PNG │ ├── helmet-2.PNG │ └── rate-limit-1.PNG └── logo.jpg ├── marking-rurbic ├── practical-marking-rubric.docx └── project-marking-rubric.docx └── tex ├── course-directive.tex └── project-descriptor.tex /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | lerna-debug.log* 8 | .pnpm-debug.log* 9 | 10 | # Diagnostic reports (https://nodejs.org/api/report.html) 11 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 12 | 13 | # Runtime data 14 | pids 15 | *.pid 16 | *.seed 17 | *.pid.lock 18 | 19 | # Directory for instrumented libs generated by jscoverage/JSCover 20 | lib-cov 21 | 22 | # Coverage directory used by tools like istanbul 23 | coverage 24 | *.lcov 25 | 26 | # nyc test coverage 27 | .nyc_output 28 | 29 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 30 | .grunt 31 | 32 | # Bower dependency directory (https://bower.io/) 33 | bower_components 34 | 35 | # node-waf configuration 36 | .lock-wscript 37 | 38 | # Compiled binary addons (https://nodejs.org/api/addons.html) 39 | build/Release 40 | 41 | # Dependency directories 42 | node_modules/ 43 | jspm_packages/ 44 | 45 | # Snowpack dependency directory (https://snowpack.dev/) 46 | web_modules/ 47 | 48 | # TypeScript cache 49 | *.tsbuildinfo 50 | 51 | # Optional npm cache directory 52 | .npm 53 | 54 | # Optional eslint cache 55 | .eslintcache 56 | 57 | # Optional stylelint cache 58 | .stylelintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variable files 76 | .env 77 | .env.development.local 78 | .env.test.local 79 | .env.production.local 80 | .env.local 81 | 82 | # parcel-bundler cache (https://parceljs.org/) 83 | .cache 84 | .parcel-cache 85 | 86 | # Next.js build output 87 | .next 88 | out 89 | 90 | # Nuxt.js build / generate output 91 | .nuxt 92 | dist 93 | 94 | # Gatsby files 95 | .cache/ 96 | # Comment in the public line in if your project uses Gatsby and not Next.js 97 | # https://nextjs.org/blog/next-9-1#public-directory-support 98 | # public 99 | 100 | # vuepress build output 101 | .vuepress/dist 102 | 103 | # vuepress v2.x temp and cache directory 104 | .temp 105 | .cache 106 | 107 | # Serverless directories 108 | .serverless/ 109 | 110 | # FuseBox cache 111 | .fusebox/ 112 | 113 | # DynamoDB Local files 114 | .dynamodb/ 115 | 116 | # TernJS port file 117 | .tern-port 118 | 119 | # Stores VSCode versions used for testing VSCode extensions 120 | .vscode-test 121 | 122 | # yarn v2 123 | .yarn/cache 124 | .yarn/unplugged 125 | .yarn/build-state.yml 126 | .yarn/install-state.gz 127 | .pnp.* 128 | 129 | # Latex 130 | *.aux 131 | *.fdb_latexmk 132 | *.fls 133 | *.log 134 | *.nav 135 | *.out 136 | *.snm 137 | *.synctex.gz 138 | *.toc 139 | *.vrb 140 | *.xdv 141 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ID607001-intro-app-dev-concepts 2 | -------------------------------------------------------------------------------- /assessments/project-descriptor.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/assessments/project-descriptor.pdf -------------------------------------------------------------------------------- /assessments/project-marking-rubric.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/assessments/project-marking-rubric.pdf -------------------------------------------------------------------------------- /course-directive.pdf: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/course-directive.pdf -------------------------------------------------------------------------------- /lecture-notes/week-01-formative-assessment.js: -------------------------------------------------------------------------------- 1 | console.log("Hello, World!"); 2 | 3 | { 4 | // Write your solution for question 1 here 5 | } 6 | 7 | { 8 | // Write your solution for question 2 here 9 | } 10 | 11 | { 12 | // Write your solution for question 3 here 13 | } 14 | 15 | { 16 | // Write your solution for question 4 here 17 | } 18 | 19 | { 20 | // Write your solution for question 5 here 21 | } 22 | 23 | { 24 | // Write your solution for question 6 here 25 | } 26 | 27 | { 28 | // Write your solution for question 7 here 29 | } 30 | 31 | { 32 | // Write your solution for question 8 here 33 | } 34 | 35 | { 36 | // Write your solution for question 9 here 37 | } 38 | 39 | { 40 | // Write your solution for question 10 here 41 | } 42 | 43 | { 44 | // Write your solution for question 11 here 45 | } 46 | 47 | { 48 | // Write your solution for question 12 here 49 | } 50 | 51 | { 52 | // Write your solution for question 13 here 53 | } 54 | 55 | { 56 | // Write your solution for question 14 here 57 | } 58 | 59 | { 60 | // Write your solution for question 15 here 61 | } 62 | 63 | { 64 | // Write your solution for question 16 here 65 | } 66 | 67 | { 68 | // Write your solution for question 17 here 69 | } 70 | 71 | { 72 | // Write your solution for question 18 here 73 | } 74 | 75 | { 76 | // Write your solution for question 19 here 77 | } 78 | 79 | { 80 | // Write your solution for question 20 here 81 | } 82 | -------------------------------------------------------------------------------- /lecture-notes/week-01-github-javascript.md: -------------------------------------------------------------------------------- 1 | # Week 01 2 | 3 | ## GitHub 4 | 5 | This course will use **GitHub** and **GitHub Classroom** to manage our development. Begin by clicking this link . You will be prompted to accept an assignment. Click on the **Accept this assignment** button. **GitHub Classroom** will create a new repository. You will use this repository to submit your formative (non-graded) and summative (graded) assessments. 6 | 7 | --- 8 | 9 | ### Development Workflow 10 | 11 | By default, **GitHub Classroom** creates an empty repository. Firstly, you must create a **README** and `.gitignore` file. **GitHub** allows new files to be created once the repository is created. 12 | 13 | --- 14 | 15 | ### Create a README 16 | 17 | Click the **Add file** button, then the **Create new file** button. Name your file `README.md` (Markdown), then click on the **Commit new file** button. You should see a new file in your formative assessments repository called `README.md` and the `main` branch. 18 | 19 | > **Resource:** 20 | 21 | --- 22 | 23 | ### Create a .gitignore File 24 | 25 | Like before, click the **Add file** button and then the **Create new file** button. Name your file `.gitignore`. A `.gitignore` template dropdown will appear on the right-hand side of the screen. Select the **Node** `.gitignore` template. Click on the **Commit new file** button. You should see a new file in your formative assessments repository called `.gitignore`. 26 | 27 | > **Resource:** 28 | 29 | --- 30 | 31 | ### Clone a Repository 32 | 33 | Open up **Git Bash** or whatever alternative you see fit on your computer. Clone your formative assessments repository to a location on your computer using the command: `git clone `. 34 | 35 | > **Resource:** 36 | 37 | --- 38 | 39 | ### Commit Message Conventions 40 | 41 | You should follow the **conventional commits** convention when committing changes to your repository. A **conventional commit** consists of a **type**, **scope** and **description**. The **type** and **description** are mandatory, while the **scope** is optional. The **type** must be one of the following: 42 | 43 | - **build**: Changes that affect the build system or external dependencies 44 | - **chore**: Regular code maintenance, such as refactoring or updating dependencies 45 | - **ci**: Changes to our CI configuration files and scripts 46 | - **docs**: Documentation only changes 47 | - **feat**: A new feature 48 | - **fix**: A bug fix 49 | - **perf**: A code change that improves performance 50 | - **refactor**: A code change that neither fixes a bug nor adds a feature 51 | - **style**: Changes that do not affect the meaning of the code (white-space, formatting, missing semi-colons, etc) 52 | - **test**: Adding missing tests or correcting existing tests 53 | 54 | The **scope** is a phrase describing the codebase section affected by the change. For example, you can use the scope `javascript` if you are working on the **formative assessment** for **JavaScript**. If you are working on the **formative assessment** for **HTML**, use the scope `html`. 55 | 56 | The **description** is a short description of the change. It should be written in the imperative mood, meaning it should be written as if you are giving a command or instruction. For example, "add a new feature" instead of "added a new feature". 57 | 58 | Here are some examples of **conventional commits**: 59 | 60 | - `feat(javascript): add a new feature` 61 | - `fix(html): fix a bug` 62 | - `docs(css): update documentation` 63 | 64 | > **Resource:** 65 | 66 | --- 67 | 68 | ## JavaScript 69 | 70 | **JavaScript** is a high-level, interpreted programming language that conforms to the **ECMAScript** specification. It is a versatile language used for both **frontend/client-side** and **backend/server-side** development. **JavaScript** is primarily used for enhancing user interactions on websites, creating **web applications**, and building **backend/server-side** applications. 71 | 72 | > **Resource:** 73 | 74 | --- 75 | 76 | ### Node.js 77 | 78 | **Node.js** is an open-source **JavaScript** runtime environment allowing you to execute **JavaScript** code outside a web browser. It is built on **Chrome's V8 JavaScript engine** and provides a rich library of various **JavaScript** modules. **Node.js** is primarily used for **backend/server-side** development. We will use **Node.js** to run **JavaScript** code. It will allow us to test our code and see the results in the terminal without opening a web browser. 79 | 80 | > **Resource:** 81 | 82 | --- 83 | 84 | ### V8 JavaScript Engine 85 | 86 | **V8** is an open-source **JavaScript** engine developed by **Google** for **Google Chrome** and **Chromium** web browsers. It is written in **C++** and is used to execute **JavaScript** code in the browser. **Node.js** uses the **V8** engine to execute **JavaScript** code outside the browser. 87 | 88 | > **Resource:** 89 | 90 | --- 91 | 92 | ### Data Types 93 | 94 | **Data types** are the different values that can be stored and manipulated in a program. **JavaScript** has seven primitive data types: 95 | 96 | - **Boolean**: `true` or `false` 97 | - **Number**: `1`, `2.5`, `-3` 98 | - **String**: `"Hello"`, `"World"` 99 | - **Null**: `null` 100 | - **Undefined**: `undefined` 101 | - **BigInt**: `9007199254740991n` 102 | - **Symbol**: `Symbol()` 103 | 104 | We will only be concerned with the first five primitive data types for now. We will not use **BigInt** and **Symbol** in this course. 105 | 106 | > **Resource:** 107 | 108 | --- 109 | 110 | ### Variables 111 | 112 | A **variable** is a named container that stores a value. It is like a box that holds a value, and the variable's name is like a label on the box. You can use the variable's name to access its value. 113 | 114 | ```javascript 115 | // A mutable variable named "name" with value "John" 116 | let name = "John"; 117 | 118 | // An immutable variable named "age" with the value 25 119 | const age = 25; 120 | ``` 121 | 122 | A variable declared with `let` is mutable, meaning its value can be changed. A variable declared with `const` is immutable, meaning its value cannot be changed. You might see `var` being used instead of `let` or `const`. `var` is an older way of declaring variables, and it has some differences in behaviour compared to `let` and `const`. For now, we will use `let` and `const`. 123 | 124 | > **Resource:** 125 | 126 | --- 127 | 128 | ### Operators 129 | 130 | **Operators** are symbols that perform operations on values. There are several types of operators in **JavaScript**: 131 | 132 | - **Arithmetic operators**: `+`, `-`, `*`, `/`, `%`, `**` 133 | - **Assignment operators**: `=`, `+=`, `-=`, `*=`, `/=`, `%=`, `**=` 134 | - **Comparison operators**: `==`, `!=`, `===`, `!==`, `>`, `<`, `>=`, `<=` 135 | - **Logical operators**: `&&`, `||`, `!` 136 | - **Conditional (ternary) operator**: `condition ? expressionIfTrue : expressionIfFalse` 137 | 138 | There are many other operators in **JavaScript**, but we will only be concerned with these for now. 139 | 140 | > **Resource:** 141 | 142 | --- 143 | 144 | ### Conditional Statements 145 | 146 | **Conditionals statements** allow you to execute different blocks of code depending on whether a condition is `true` or `false`. There are three types of conditionals in **JavaScript**: 147 | 148 | - **if statement** 149 | - **if...else statement** 150 | - **switch statement** 151 | 152 | ```javascript 153 | // if statement 154 | if (condition) { 155 | // code to execute if condition is true 156 | } 157 | 158 | // if...else statement 159 | if (condition) { 160 | // code to execute if condition is true 161 | } else { 162 | // code to execute if condition is false 163 | } 164 | 165 | // switch statement 166 | switch (expression) { 167 | case value1: 168 | // code to execute if expression is equal to value1 169 | break; 170 | case value2: 171 | // code to execute if expression is equal to value2 172 | break; 173 | default: 174 | // code to execute if expression is not equal to any of the values 175 | } 176 | ``` 177 | 178 | > **Resource:** 179 | 180 | --- 181 | 182 | ### Loops 183 | 184 | **Loops** are statements that allow you to execute a block of code repeatedly. There are several types of **loops** in **JavaScript**: 185 | 186 | - **for loop** 187 | - **while loop** 188 | - **do...while loop** 189 | - **for...in loop** 190 | - **for...of loop** 191 | - **forEach() method** 192 | 193 | ```javascript 194 | // for loop 195 | for (let i = 0; i < 10; i++) { 196 | // code to execute repeatedly 197 | } 198 | 199 | // while loop 200 | while (condition) { 201 | // code to execute repeatedly 202 | } 203 | 204 | // do...while loop 205 | do { 206 | // code to execute repeatedly 207 | } while (condition); 208 | 209 | // for...in loop 210 | for (let key in object) { 211 | // code to execute repeatedly 212 | } 213 | 214 | // for...of loop 215 | for (let element of array) { 216 | // code to execute repeatedly 217 | } 218 | 219 | // forEach() method 220 | array.forEach(function (element) { 221 | // code to execute repeatedly 222 | }); 223 | ``` 224 | 225 | Feel free to read up on the differences between these **loops**. 226 | 227 | > **Resource:** 228 | 229 | --- 230 | 231 | ### Functions 232 | 233 | A **function** is a block of code that performs a specific task. It is like a machine that takes in some input, performs some operations, and returns some output. A function is a reusable piece of code you can use in your program. 234 | 235 | ```javascript 236 | // A function named "add" that takes in two numbers and returns their sum 237 | function add(num1, num2) { 238 | return num1 + num2; 239 | } 240 | 241 | console.log(add(1, 2)); // 3 242 | 243 | // A function named "greet" that takes in a name and returns a greeting 244 | function greet(name) { 245 | return "Hello, " + name + "!"; 246 | } 247 | 248 | console.log(greet("John")); // Hello, John! 249 | ``` 250 | 251 | An **arrow function** is a newer way of declaring a **function**. 252 | 253 | ```javascript 254 | // An arrow function named "add" that takes in two numbers and returns their sum 255 | const add = (num1, num2) => { 256 | return num1 + num2; 257 | }; 258 | 259 | console.log(add(1, 2)); // 3 260 | 261 | // An arrow function named "greet" that takes in a name and returns a greeting 262 | const greet = (name) => { 263 | return "Hello, " + name + "!"; 264 | }; 265 | 266 | console.log(greet("John")); // Hello, John! 267 | ``` 268 | 269 | If a **function** only has one statement, you can omit the curly braces and the `return` keyword. 270 | 271 | ```javascript 272 | // An arrow function named "add" that takes in two numbers and returns their sum 273 | const add = (num1, num2) => num1 + num2; 274 | 275 | console.log(add(1, 2)); // 3 276 | 277 | // An arrow function named "greet" that takes in a name and returns a greeting 278 | const greet = (name) => "Hello, " + name + "!"; 279 | 280 | console.log(greet("John")); // Hello, John! 281 | ``` 282 | 283 | If a **function** does not take in any parameters, you can omit the parentheses. 284 | 285 | ```javascript 286 | // An arrow function named "greet" that returns a greeting 287 | const greet = (_) => "Hello, World!"; 288 | 289 | console.log(greet()); // Hello, World! 290 | ``` 291 | 292 | > **Resource:** 293 | 294 | --- 295 | 296 | ### Arrays 297 | 298 | An **array**, also known as a **one-dimensional array** is a data structure that stores a list of values. It is like a box that can hold multiple values, and each value is assigned an index starting from 0. You can use a value's index to access its value. 299 | 300 | ```javascript 301 | // An array of numbers 302 | const numbers = [1, 2, 3, 4, 5]; 303 | 304 | // An array of strings 305 | const fruits = ["Apple", "Banana", "Cherry", "Durian", "Elderberry"]; 306 | ``` 307 | 308 | Here is an example of an **array** with values of different data types. 309 | 310 | ```javascript 311 | // An array of different data types 312 | const mixed = [1, "Hello", true, null, undefined]; 313 | ``` 314 | 315 | A **2D array**, also known as a **two-dimensional array**, is a data structure that represents a matrix or a grid-like structure with rows and columns. 316 | 317 | ```javascript 318 | // A 2D array of numbers 319 | const numbers = [ 320 | [1, 2, 3], 321 | [4, 5, 6], 322 | ]; 323 | 324 | // A 2D array of strings 325 | const fruits = [ 326 | ["Apple", "Banana", "Cherry"], 327 | ["Durian", "Elderberry", "Fig"], 328 | ]; 329 | 330 | console.log(numbers[0][0]); // 1 331 | console.log(fruits[1][2]); // Fig 332 | ``` 333 | 334 | Here is an example of a **2D array** with **arrays** of different lengths. 335 | 336 | ```javascript 337 | // A 2D array of different lengths 338 | const mixed = [ 339 | [1, 2, 3], 340 | ["Hello", "World"], 341 | [true, false], 342 | ]; 343 | ``` 344 | 345 | > **Resource:** 346 | 347 | --- 348 | 349 | ### Map, Filter and Reduce 350 | 351 | **Map**, **filter** and **reduce** are higher-order functions that are commonly used with **arrays**. 352 | 353 | **Map** transforms an **array** by applying a **function** to each element in the **array** and returning a new **array**. Here is an example of using the `map` function. 354 | 355 | ```javascript 356 | // An array of numbers 357 | const numbers = [1, 2, 3, 4, 5]; 358 | 359 | // Map 360 | const numbersSquared = numbers.map((num) => num * num); 361 | 362 | console.log(numbersSquared); // [1, 4, 9, 16, 25] 363 | 364 | // Passing a named function to map 365 | function square(num) { 366 | return num * num; 367 | } 368 | 369 | const numbersSquared = numbers.map(square); 370 | 371 | console.log(numbersSquared); // [1, 4, 9, 16, 25] 372 | ``` 373 | 374 | **Filter** filters an **array** by removing elements not satisfying a condition and returning a new **array**. Here is an example of using the `filter` function. 375 | 376 | ```javascript 377 | // An array of numbers 378 | const numbers = [1, 2, 3, 4, 5]; 379 | 380 | // Filter 381 | const evenNumbers = numbers.filter((num) => num % 2 === 0); 382 | 383 | console.log(evenNumbers); // [2, 4] 384 | 385 | // Passing a named function to filter 386 | function isEven(num) { 387 | return num % 2 === 0; 388 | } 389 | 390 | const evenNumbers = numbers.filter(isEven); 391 | 392 | console.log(evenNumbers); // [2, 4] 393 | ``` 394 | 395 | **Reduce** reduces an **array** to a single value by applying a **function** to each element in the **array** and returning a single value. Here is an example of using the `reduce` function. 396 | 397 | ```javascript 398 | // Reduce 399 | const sum = numbers.reduce((total, num) => total + num, 0); 400 | 401 | console.log(sum); // 15 402 | 403 | // Passing a named function to reduce 404 | function add(total, num) { 405 | return total + num; 406 | } 407 | 408 | const sum = numbers.reduce(add, 0); 409 | 410 | console.log(sum); // 15 411 | ``` 412 | 413 | > **Note:** For **map** and **filter**, the original **array** is not modified. 414 | 415 | --- 416 | 417 | ### Objects 418 | 419 | An **object** is a data structure that stores a collection of key-value pairs. It is like a box that can hold multiple key-value pairs, assigning each key a value. You can use the key of a key-value pair to access its value. 420 | 421 | ```javascript 422 | // An object with key-value pairs 423 | const person = { 424 | name: "John", 425 | age: 25, 426 | isMale: true, 427 | }; 428 | 429 | console.log(person.name); // John 430 | console.log(person.age); // 25 431 | console.log(person.isMale); // true 432 | ``` 433 | 434 | Here is another example of an **object** with key-value pairs of different data types. 435 | 436 | ```javascript 437 | // An object with key-value pairs of different data types 438 | const person = { 439 | name: "John", 440 | age: 25, 441 | isMale: true, 442 | favouriteFruits: ["Apple", "Banana", "Cherry"], 443 | greet: () => "Hello, " + this.name + "!", 444 | }; 445 | 446 | console.log(person.favouriteFruits[0]); // Apple 447 | console.log(person.greet()); // Hello, John! 448 | ``` 449 | 450 | You can have an **array** of **objects** or an **object** with **arrays**. Here is an example of an **array** of **objects**. 451 | 452 | ```javascript 453 | // An array of objects 454 | const people = [ 455 | { 456 | name: "John", 457 | age: 25, 458 | isMale: true, 459 | }, 460 | { 461 | name: "Jane", 462 | age: 20, 463 | isMale: false, 464 | }, 465 | ]; 466 | 467 | console.log(people[0].name); // John 468 | console.log(people[1].age); // 20 469 | ``` 470 | 471 | There is an alternative way to create an **object** using the **Object** constructor. 472 | 473 | ```javascript 474 | // An object using the Object constructor 475 | const person = new Object(); 476 | person.name = "John"; 477 | person.age = 25; 478 | person.isMale = true; 479 | 480 | console.log(person.name); // John 481 | console.log(person.age); // 25 482 | ``` 483 | 484 | In this course, we will use the **object literal** syntax to create **objects**. 485 | 486 | > **Resource:** 487 | 488 | --- 489 | 490 | ## Formative Assessment 491 | 492 | Copy the file `week-01-formative-assessment.js` into your **s1-25-intro-app-dev-repo** repository. Open your **s1-25-intro-app-dev-repo** repository in **Visual Studio Code**. Open the terminal and run the command `node week-01-formative-assessment.js` to run the file. You should see the following output. 493 | 494 | ```bash 495 | $ node week-01-formative-assessment.js 496 | Hello, World! 497 | ``` 498 | 499 | Create a new branch called **week-01-formative-assessment**. 500 | 501 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 502 | 503 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 504 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 505 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 506 | --- 507 | 508 | ### Task One 509 | 510 | Write a **function** to check whether a given number is prime. 511 | 512 | - Test case 1: `isPrime(1)` should return `false` 513 | - Test case 2: `isPrime(2)` should return `true` 514 | 515 | > **Hint on how to solve this task:** A prime number is a number greater than 1 that has no positive divisors other than 1 and itself. You can check if a number is prime by iterating from 2 to the square root of the number and checking if the number is divisible by any of the numbers in that range. 516 | 517 | --- 518 | 519 | ### Task Two 520 | 521 | Write a **function** to reverse a string. 522 | 523 | - Test case 1: `reverseString("Hello")` should return `"olleH"` 524 | - Test case 2: `reverseString("World")` should return `"dlroW"` 525 | 526 | > **Hint on how to solve this task:** You can reverse a string by converting it to an **array**, reversing the **array**, and then converting the **array** back to a string. There are three functions you can use to achieve this: `split()`, `reverse()`, and `join()`. You can also use a **for loop** to reverse a string. 527 | 528 | --- 529 | 530 | ### Task Three 531 | 532 | Write a function to find the maximum element in an array. 533 | 534 | - Test case 1: `findMax([1, 2, 3, 4, 5])` should return `5` 535 | - Test case 2: `findMax([5, 4, 3, 2, 1])` should return `5` 536 | - Test case 3: `findMax([1, 3, 5, 2, 4])` should return `5` 537 | 538 | > **Hint on how to solve this task:** You can find the maximum element in an **array** by iterating through the **array** and keeping track of the maximum element found so far. You can start by assuming the first element is the maximum element and then compare it with the rest of the elements in the **array**. 539 | 540 | --- 541 | 542 | ### Task Four 543 | 544 | Write a **function** to check whether a given string is a palindrome. 545 | 546 | - Test case 1: `isPalindrome("racecar")` should return `true` 547 | - Test case 2: `isPalindrome("rAcEcAr")` should return `true` 548 | - Test case 2: `isPalindrome("hello")` should return `false` 549 | 550 | > **Hint on how to solve this task:** A palindrome is a word, phrase, number, or other sequence of characters that reads the same forward and backward. You can check if a string is a palindrome by comparing the string with its reverse. You can use the **function** you wrote in Task Two to reverse the string. 551 | 552 | --- 553 | 554 | ### Task Five 555 | 556 | Write a **function** to calculate the factorial of a number. 557 | 558 | - Test case 1: `factorial(0)` should return `1` 559 | - Test case 2: `factorial(1)` should return `1` 560 | - Test case 3: `factorial(5)` should return `120` 561 | 562 | > **Hint on how to solve this task:** The factorial of a non-negative integer `n` is the product of all positive integers less than or equal to `n`. You can calculate the factorial of a number by multiplying all positive integers less than or equal to that number. You can use a **for loop** to calculate the factorial of a number. You can also use recursion to calculate the factorial of a number but we will not cover recursion in this course. 563 | 564 | --- 565 | 566 | ### Task Six 567 | 568 | Write a **function** to sort an array of numbers in ascending order. 569 | 570 | - Test case 1: `sort([5, 4, 3, 2, 1])` should return `[1, 2, 3, 4, 5]` 571 | - Test case 2: `sort([1, 2, 3, 4, 5])` should return `[1, 2, 3, 4, 5]` 572 | - Test case 3: `sort([1, 3, 5, 2, 4])` should return `[1, 2, 3, 4, 5]` 573 | 574 | > **Hint on how to solve this task:** You can sort an **array** of numbers in ascending order by using the `sort` method. The `sort` method sorts the elements of an **array** in place and returns the sorted **array**. By default, the `sort` method sorts the elements as strings. You can provide a **compare function** to the `sort` method to sort the elements as numbers. 575 | 576 | --- 577 | 578 | ### Task Seven 579 | 580 | Write a **function** to count the number of occurrences of a specific element in an array. 581 | 582 | - Test case 1: `count([1, 2, 3, 4, 5], 1)` should return `1` 583 | - Test case 2: `count([1, 2, 3, 4, 5], 6)` should return `0` 584 | - Test case 3: `count([1, 2, 3, 4, 5, 1], 1)` should return `2` 585 | 586 | > **Hint on how to solve this task:** You can count the number of occurrences of a specific element in an **array** by iterating through the **array** and keeping track of the number of occurrences of that element. You can start by assuming the number of occurrences is 0 and then increment it each time you find the element in the **array**. You can use a **for loop** to count the number of occurrences of a specific element in an **array**. 587 | 588 | --- 589 | 590 | ### Task Eight 591 | 592 | Write a **function** to check whether two strings are anagrams of each other. 593 | 594 | - Test case 1: `isAnagram("hello", "olleh")` should return `true` 595 | - Test case 2: `isAnagram("hello", "world")` should return `false` 596 | 597 | > **Hint on how to solve this task:** An anagram is a word or phrase formed by rearranging the letters of a different word or phrase, typically using all the original letters exactly once. You can check if two strings are anagrams of each other by comparing the sorted strings. You can use the **function** you wrote in Task Six to sort the strings. 598 | 599 | --- 600 | 601 | ### Task Nine 602 | 603 | Write a **function** to find the longest word in a string. 604 | 605 | - Test case 1: `findLongestWord("The quick brown fox jumped over the lazy dog")` should return `"jumped"` 606 | - Test case 2: `findLongestWord("May the force be with you")` should return `"force"` 607 | 608 | > **Hint on how to solve this task:** You can find the longest word in a string by splitting the string into an **array** of words, finding the length of each word, and keeping track of the longest word found so far. You can start by assuming the first word is the longest word and then compare it with the rest of the words in the **array**. 609 | 610 | --- 611 | 612 | ### Task Ten 613 | 614 | Write a **function** to merge two sorted **arrays** into a single sorted **array**. 615 | 616 | - Test case 1: `merge([1, 2, 3], [4, 5, 6])` should return `[1, 2, 3, 4, 5, 6]` 617 | - Test case 2: `merge([4, 5, 6], [1, 2, 3])` should return `[1, 2, 3, 4, 5, 6]` 618 | 619 | > **Hint on how to solve this task:** You can merge two sorted **arrays** into a single sorted **array** by iterating through both **arrays** and comparing the elements in each **array**. You can start by assuming the first element in each **array** is the smallest element and then compare them. You can use two pointers to keep track of the current index in each **array**. 620 | 621 | --- 622 | 623 | ### Task Eleven 624 | 625 | Given an **array** of **objects** representing learners with properties `name` and `age`, use the `map` function to create a new **array** of **strings** that contains a message for each learner in the format "name is age years old". 626 | 627 | ```javascript 628 | // An array of objects 629 | const learners = [ 630 | { name: "Alice", age: 21 }, 631 | { name: "Bob", age: 19 }, 632 | { name: "Charlie", age: 20 }, 633 | ]; 634 | 635 | // Expected output 636 | ["Alice is 21 years old", "Bob is 19 years old", "Charlie is 20 years old"]; 637 | ``` 638 | 639 | > **Hint on how to solve this task:** You can create a new **array** of **strings** by iterating through the **array** of **objects** and creating a message for each learner. You can use the `map` function to create a new **array** of **strings**. 640 | 641 | --- 642 | 643 | ### Task Twelve 644 | 645 | Given an **array** of **objects** representing learners with properties `name` and `age`, use the `filter` and `map` functions to create a new **array** of **objects** that contains only learners older than 20. 646 | 647 | ```javascript 648 | // An array of objects 649 | const learners = [ 650 | { name: "Alice", age: 21 }, 651 | { name: "Bob", age: 19 }, 652 | { name: "Charlie", age: 25 }, 653 | { name: "David", age: 18 }, 654 | { name: "Eve", age: 22 }, 655 | ]; 656 | 657 | // Expected output 658 | // [ 659 | // { name: "Alice", age: 21 }, 660 | // { name: "Charlie", age: 25 }, 661 | // { name: "Eve", age: 22 }, 662 | // ] 663 | ``` 664 | 665 | > **Hint on how to solve this task:** You can create a new **array** of **objects** by filtering the **array** of **objects** to include only learners older than 20 and then mapping the filtered **array** of **objects** to create a new **array** of **objects**. 666 | 667 | --- 668 | 669 | ### Task Thirteen 670 | 671 | Given an **array** of **objects** representing learners with properties `name` and `age`, use the `filter` and `map` functions to create a new **array** of **objects** that contains only learners older than 20 and younger than 25. 672 | 673 | ```javascript 674 | // An array of objects 675 | const learners = [ 676 | { name: "Alice", age: 21 }, 677 | { name: "Bob", age: 19 }, 678 | { name: "Charlie", age: 25 }, 679 | { name: "David", age: 18 }, 680 | { name: "Eve", age: 22 }, 681 | ]; 682 | 683 | // Expected output 684 | // [ 685 | // { name: "Alice", age: 21 }, 686 | // { name: "Eve", age: 22 }, 687 | // ] 688 | ``` 689 | 690 | > **Hint on how to solve this task:** You can create a new **array** of **objects** by filtering the **array** of **objects** to include only learners older than 20 and younger than 25 and then mapping the filtered **array** of **objects** to create a new **array** of **objects**. 691 | 692 | --- 693 | 694 | ### Task Fourteen 695 | 696 | Given an **array** of **strings**, use the `filter` and `map` functions create a new **array** that contains the lengths of each string, excluding any string that starts with the letter "A". 697 | 698 | ```javascript 699 | // An array of strings 700 | const words = ["Apple", "Banana", "Avocado", "Strawberry", "Mango"]; 701 | 702 | // Expected output 703 | // [6, 10, 5] 704 | ``` 705 | 706 | > **Hint on how to solve this task:** You can create a new **array** of lengths by filtering the **array** of **strings** to exclude any string that starts with the letter "A" and then mapping the filtered **array** of **strings** to create a new **array** of lengths. 707 | 708 | --- 709 | 710 | ### Task Fifteen 711 | 712 | Given an **array** of **numbers**, use the `reduce` function to calculate the average grade of the learners and return the result. 713 | 714 | ```javascript 715 | // An array of numbers 716 | const grades = [85, 90, 78, 92, 88]; 717 | 718 | // Expected output 719 | // 86.6 720 | ``` 721 | 722 | > **Hint on how to solve this task:** You can calculate the average grade of the learners by using the `reduce` function to sum the grades and then dividing the sum by the number of grades. You can use the `length` property of the **array** to get the number of grades. 723 | 724 | --- 725 | 726 | ### Task Sixteen 727 | 728 | Given an **array** of **strings**, use the `reduce` function to count the occurrences of each flavour and return an object that represents the frequency of each flavour. 729 | 730 | ```javascript 731 | // An array of strings 732 | const flavours = ["chocolate", "vanilla", "chocolate", "strawberry", "vanilla"]; 733 | 734 | // Expected output 735 | // { 736 | // chocolate: 2, 737 | // vanilla: 2, 738 | // strawberry: 1, 739 | // } 740 | ``` 741 | 742 | > **Hint on how to solve this task:** You can count the occurrences of each flavour by using the `reduce` function to create an object that represents the frequency of each flavour. You can start by assuming the object is empty and then increment the count of each flavour each time you find the flavour in the **array**. 743 | 744 | --- 745 | 746 | ### Task Seventeen 747 | 748 | Given a **2D array** of **numbers**, matrix, write a **function** that finds the maximum value in the entire matrix. 749 | 750 | ```javascript 751 | // A 2D array of numbers 752 | const matrix = [ 753 | [1, 2, 3], 754 | [4, 5, 6], 755 | [7, 8, 9], 756 | ]; 757 | 758 | const max = findMaxValue(matrix); 759 | console.log(max); 760 | 761 | // Expected output 762 | // 9 763 | ``` 764 | 765 | > **Hint on how to solve this task:** You can find the maximum value in the entire matrix by iterating through the **2D array** and keeping track of the maximum value found so far. You can start by assuming the first element is the maximum value and then compare it with the rest of the elements in the **2D array**. 766 | 767 | --- 768 | 769 | ### Task Eighteen 770 | 771 | Write a **function** that generates a multiplication table from 1 to a given number, `n`. The multiplication table should be represented as a **2D array**, where each element at index `[i][j]` represents the product of `i + 1` and `j + 1`. 772 | 773 | ```javascript 774 | const multiplicationTable = generateMultiplicationTable(5); 775 | console.log(multiplicationTable); 776 | 777 | // Expected output 778 | // [ 779 | // [1, 2, 3, 4, 5], 780 | // [2, 4, 6, 8, 10], 781 | // [3, 6, 9, 12, 15], 782 | // [4, 8, 12, 16, 20], 783 | // [5, 10, 15, 20, 25], 784 | // ] 785 | ``` 786 | 787 | > **Hint on how to solve this task:** You can generate a multiplication table by creating a **2D array** and then iterating through the **2D array** to calculate the product of `i + 1` and `j + 1`. You can use a **nested for loop** to generate the multiplication table. 788 | 789 | --- 790 | 791 | ### Task Nineteen 792 | 793 | A cinema has `n` rows and `m` seats in each row. The seating arrangement is represented by a 2D array, where `0` indicates an empty seat and `1` indicates an occupied seat. Write a **function** that finds the number of available seats in the cinema. 794 | 795 | ```javascript 796 | // A 2D array of numbers 797 | const seatingArrangement = [ 798 | [0, 0, 1, 0, 1], 799 | [1, 0, 1, 1, 0], 800 | [0, 0, 0, 1, 0], 801 | [1, 0, 0, 0, 0], 802 | ]; 803 | 804 | const availableSeats = countAvailableSeats(seatingArrangement); 805 | console.log(availableSeats); 806 | 807 | // Expected output 808 | // 13 809 | ``` 810 | 811 | > **Hint on how to solve this task:** You can find the number of available seats in the cinema by iterating through the **2D array** and counting the number of empty seats. You can start by assuming the number of available seats is 0 and then increment it each time you find an empty seat in the **2D array**. 812 | 813 | --- 814 | 815 | ### Task Twenty 816 | 817 | Write a **function** that checks the winner of a **Tic-Tac-Toe** game represented by a **2D array**. The board is a 3x3 grid, where "X" represents Player X's move, "O" represents Player O's move, and "-" represents a space. The **function** should determine the winner or declare it as a tie. 818 | 819 | ```javascript 820 | // A 2D array representing a Tic-Tac-Toe board 821 | const board = [ 822 | ["X", "O", "-"], 823 | ["-", "X", "O"], 824 | ["-", "-", "X"], 825 | ]; 826 | 827 | const winner = checkWinner(board); 828 | console.log(winner); 829 | 830 | // Expected output 831 | // "X" 832 | ``` 833 | 834 | > **Hint on how to solve this task:** You can check the winner of a **Tic-Tac-Toe** game by checking the rows, columns, and diagonals of the **2D array**. You can start by checking the rows and then the columns and diagonals. You can use a series of **if statements** to check the rows, columns, and diagonals for a winner. 835 | 836 | --- 837 | 838 | ## Next Class 839 | 840 | Link to the next class: [Week 02](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-02-express-http.md) 841 | -------------------------------------------------------------------------------- /lecture-notes/week-02-rest-express-http.md: -------------------------------------------------------------------------------- 1 | ## Week 02 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 01](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-01-github-javascript.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-02-formative-assessment** from **week-01-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## Express 18 | 19 | **Express** is a web application framework for **Node.js**. It is designed for building web applications and APIs. It has been called the de facto standard server framework for **Node.js**. 20 | 21 | > **Resource:** 22 | 23 | --- 24 | 25 | ### Application Programming Interface (API) 26 | 27 | You have come across different interfaces before. For example, **Graphical User Interface (GUI)** and **Command Line Interface (CLI)**. An **Application Programming Interface (API)** is a set of rules and protocols that allows different software applications to communicate with each other. 28 | 29 | What is meant by rules and protocols? 30 | 31 | - Communication protocols: The most common communication protocols are **Hypertext Transfer Protocol (HTTP)** and **Hypertext Transfer Protocol Secure (HTTPS)**. The protocol is used to send and receive data between different software applications. 32 | - Request methods: The most common request methods are **GET (retrieving data)**, **POST (creating data)**, **PUT (updating data)**, and **DELETE (deleting data)**. For example, the `GET` method is used to retrieve data. 33 | - Data formats: The most common data formats are **JavaScript Object Notation (JSON)** and **eXtensible Markup Language (XML)**. 34 | - Endpoint URLs: Used to access the different resources. For example, `/api/users` is the endpoint URL for accessing the list of users. 35 | - Authentication and authorisation: Used to restrict access to certain resources. For example, a user must be authenticated and authorised to access the list of users. 36 | - Error handling: Used to handle errors. For example, if a user tries to access a resource that does not exist, an error message should be returned. 37 | 38 | --- 39 | 40 | ### HTTP Request Methods 41 | 42 | An **HTTP request method** is a **verb** that indicates the desired action to be performed for a given resource. For example, the `GET` method requests a representation of the specified resource. 43 | 44 | There are nine different **HTTP request methods**: 45 | 46 | - `GET`: Requests a representation of the specified resource. Requests using `GET` should only retrieve data. 47 | - `HEAD`: Requests a representation of the specified resource. Requests using `HEAD` should only retrieve data. 48 | - `POST`: Submits data to be processed to the specified resource. The data is included in the body of the request. The data may result in the creation of a new resource or the updates of existing resources. 49 | - `PUT`: Replaces all current representations of the target resource with the request payload. 50 | - `DELETE`: Deletes the specified resource. 51 | - `CONNECT`: Establishes a tunnel to the server identified by the target resource. 52 | - `OPTIONS`: Describes the communication options for the target resource. 53 | - `TRACE`: Performs a message loop-back test along the path to the target resource. 54 | - `PATCH`: Used to apply partial modifications to a resource. 55 | 56 | We will only being using `GET`, `POST`, `PUT`, and `DELETE` in this course. 57 | 58 | > **Resource:** 59 | 60 | --- 61 | 62 | ### HTTP Status Codes 63 | 64 | An **HTTP response status code** indicates whether a specific **HTTP request** has been successfully completed. Responses are grouped in five classes: 65 | 66 | 1. Information responses (100–199) 67 | 2. Successful responses (200–299) 68 | 3. Redirection messages (300–399) 69 | 4. Client error responses (400–499) 70 | 5. Server error responses (500–599) 71 | 72 | > **Resource:** 73 | 74 | --- 75 | 76 | ### HTTP Headers 77 | 78 | An **HTTP header** is a **header** that is sent at the beginning of a **request** or **response**. It contains information about the **request** or **response** and about the **client** or the **server**. 79 | 80 | There are four different **header** groups: 81 | 82 | 1. Request headers 83 | 2. Response headers 84 | 3. Representation headers 85 | 4. Payload headers 86 | 87 | > **Resource:** 88 | 89 | --- 90 | 91 | ### Node Package Manager (NPM) 92 | 93 | **Node Package Manager (NPM)** is a package manager for **Node.js**. It is used to install, share, and distribute code. 94 | 95 | > **Resource:** 96 | 97 | --- 98 | 99 | ### Setup 100 | 101 | Open a terminal and run the following. 102 | 103 | ```bash 104 | npm init -y 105 | npm install express 106 | npm install nodemon --save-dev 107 | ``` 108 | 109 | What does each do? 110 | 111 | - `npm init -y`: Initialises a **Node.js** project. The `-y` flag is used to accept the default values. 112 | - `npm install express`: Installs the **Express** module. 113 | - `npm install nodemon --save-dev`: Installs the **Nodemon** module. The `--save-dev` flag is used to save the module as a development dependency. A development dependency is a module that is only required during development. It is not required in production. 114 | 115 | You will notice new files and directories in the root directory. These include: 116 | 117 | - `node_modules` 118 | - `package.json` 119 | - `package-lock.json` 120 | 121 | --- 122 | 123 | ### Node Modules 124 | 125 | The `node_modules` directory contains the modules installed by **NPM**. It is recommended to add the `node_modules` directory to the `.gitignore` file. This prevents the modules from being pushed to the repository. 126 | 127 | --- 128 | 129 | ### Package JSON File 130 | 131 | The `package.json` file is used to manage the **Node.js** project. It contains information about the project, such as the name, version, and dependencies. 132 | 133 | > **Resource:** 134 | 135 | --- 136 | 137 | ### Package Lock JSON File 138 | 139 | The `package-lock.json` file is automatically generated by **NPM**. It is used to lock the version of the modules installed. This ensures that the same version of the module is installed on different machines. 140 | 141 | > **Resource:** 142 | 143 | --- 144 | 145 | ### Scripts 146 | 147 | A **script** is a series of commands that are executed by the **Node.js** runtime. **Scripts** are used to automate repetitive tasks. 148 | 149 | In the `package.json` file, add the following line to the `scripts` block. 150 | 151 | ```json 152 | "dev": "nodemon app.js" 153 | ``` 154 | 155 | Your `scripts` block should look like this. 156 | 157 | ```json 158 | "scripts": { 159 | "test": "echo \"Error: no test specified\" && exit 1", 160 | "dev": "nodemon app.js" 161 | }, 162 | ``` 163 | 164 | The `dev` script is used to start the server in development mode. The `nodemon` module is used to restart the server when changes are made to the code. 165 | 166 | > **Resource:** 167 | 168 | --- 169 | 170 | ### Module 171 | 172 | In the `package.json` file, add the following under the `scripts` block. 173 | 174 | ```json 175 | "type": "module", 176 | ``` 177 | 178 | This will allow you to use **ES6 modules** in your project. For example, `import` and `export`, rather than `require` and `module.exports`. 179 | 180 | --- 181 | 182 | ### Main File 183 | 184 | In the root directory, create a file named `app.js`. In the `app.js` file, add the following code. 185 | 186 | ```javascript 187 | // Import the Express module 188 | import express from "express"; 189 | 190 | // Create an Express application 191 | const app = express(); 192 | 193 | // Use the PORT environment variable or 3000 194 | const PORT = process.env.PORT || 3000; 195 | 196 | // Create a GET route 197 | app.get("/", (req, res) => { 198 | return res.status(200).json({ 199 | message: "Hello, World!", 200 | }); 201 | }); 202 | 203 | // Start the server on port 3000 204 | app.listen(PORT, () => { 205 | console.log( 206 | `Server is listening on port ${PORT}. Visit http://localhost:${PORT}` 207 | ); 208 | }); 209 | 210 | // Export the Express application. May be used by other modules. For example, API testing 211 | export default app; 212 | ``` 213 | 214 | > **Note:** The `app.js` file is the entry point of the application. It is used to start the server and define the routes. 215 | 216 | --- 217 | 218 | ### Running the Server 219 | 220 | In the terminal, run the following command. 221 | 222 | ```bash 223 | npm run dev 224 | ``` 225 | 226 | This command will run the `dev` script declared in the `package.json` file. The server will start on port `3000`. 227 | 228 | Open a browser and navigate to . You should see the following message. 229 | 230 | ```bash 231 | { 232 | "message": "Hello, World!" 233 | } 234 | ``` 235 | 236 | --- 237 | 238 | ### Controller 239 | 240 | In the root directory, create a directory named `controllers`. In the `controllers` directory, create a file named `index.js` and add the following code. 241 | 242 | ```javascript 243 | // Create a GET route 244 | const getIndex = (req, res) => { 245 | // req is an object that contains information about the HTTP request. res is an object that contains information about the HTTP response. 246 | return res.status(200).json({ 247 | message: "Hello, World!", 248 | }); 249 | }; 250 | 251 | // Export the getIndex function. May be used by other modules. For example, the index routes module 252 | export { getIndex }; 253 | ``` 254 | 255 | > **Resource:** 256 | 257 | --- 258 | 259 | ### Route 260 | 261 | In the root directory, create a directory named `routes`. In the `routes` directory, create a file named `index.js` and add the following code. 262 | 263 | ```javascript 264 | // Import the Express module 265 | import express from "express"; 266 | 267 | // Import the index controllers module 268 | import { getIndex } from "../controllers/index.js"; 269 | 270 | // Create an Express router 271 | const router = express.Router(); 272 | 273 | // Create a GET route 274 | router.get("/", getIndex); 275 | 276 | // Export the router 277 | export default router; 278 | ``` 279 | 280 | In the `app.js` file, update with the following code. 281 | 282 | ```javascript 283 | // Import the Express module 284 | import express from "express"; 285 | 286 | // Import the index routes module 287 | import indexRoutes from "./routes/index.js"; 288 | 289 | // Create an Express application 290 | const app = express(); 291 | 292 | // Use the PORT environment variable or 3000 293 | const PORT = process.env.PORT || 3000; 294 | 295 | // Use the routes module 296 | app.use("/", indexRoutes); 297 | 298 | // Start the server on port 3000 299 | app.listen(PORT, () => { 300 | console.log( 301 | `Server is listening on port ${PORT}. Visit http://localhost:${PORT}` 302 | ); 303 | }); 304 | 305 | // Export the Express application. May be used by other modules. For example, API testing 306 | export default app; 307 | ``` 308 | 309 | --- 310 | 311 | ### File Structure 312 | 313 | Your file structure should look something like this. 314 | 315 | ```bash 316 | . 317 | ├── controllers 318 | │ └── index.js 319 | ├── node_modules 320 | ├── routes 321 | │ └── index.js 322 | ├── app.js 323 | ├── package-lock.json 324 | ├── package.json 325 | ``` 326 | 327 | When setting up a project, it is important to have a clear file structure. This makes it easier to find files and maintain the project. 328 | 329 | --- 330 | 331 | ## Formative Assessment 332 | 333 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 334 | 335 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 336 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 337 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 338 | 339 | --- 340 | 341 | ### Task One 342 | 343 | Implement the code examples above. 344 | 345 | --- 346 | 347 | ### Task Two 348 | 349 | To get use to creating `controllers` and `routes`, create two `GET` routes for the following. 350 | 351 | - . Return your learner id, first name, last name, email address and one thing you enjoy about IT. 352 | - . Return a list of courses you are enrolled in this semester. 353 | 354 | You should have new `controller` and `route` files for about and courses. 355 | 356 | --- 357 | 358 | ### Task Three - Prettier (Independent Research) 359 | 360 | **Prettier** is a popular code formatting tool. 361 | 362 | Read the documentation on [Prettier](https://prettier.io/docs/en/index.html), particularly the **Usage > Install**, **Usage > Ignoring Code** and **Configuring Prettier > Configuration File** sections. Use this information to format your code based on the rules specified in the `.prettierrc.json` file. 363 | 364 | In the `.prettierrc.json` file, implement the following rules: 365 | 366 | - Print width is 80 367 | - Tab width is 2 368 | - Semi-colons are required 369 | - Single quotes are required 370 | - Trailing commas wherever possible 371 | 372 | In the `package.json` file, add the following line to the `scripts` block. 373 | 374 | ```json 375 | "prettier:format": "npx prettier --write ." 376 | ``` 377 | 378 | 379 | The `prettier:format` script is used to format the code based on the rules specified in the `.prettierrc.json` file. 380 | 381 | Run the `prettier:format` script to format your code. 382 | 383 | --- 384 | 385 | ## Next Class 386 | 387 | Link to the next class: [Week 03](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-03-render-postgresql-jsdocs-swagger.md) 388 | -------------------------------------------------------------------------------- /lecture-notes/week-03-postgresql-docker-jsdoc-swagger.md: -------------------------------------------------------------------------------- 1 | # Week 03 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 02](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-02-express-http.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-03-formative-assessment** from **week-02-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## PostgreSQL 18 | 19 | **PostgreSQL** is a free relational database management system (RDBMS). It is a powerful, highly-extensible, and feature-rich database system. It is also known as **Postgres**. 20 | 21 | > **Note:** There are different types of databases. For example, **relational databases**, **NoSQL databases**, **graph databases**, etc. **PostgreSQL** is a **relational database**. **Relational databases** store data in tables. Each table has rows and columns. **SQL** (Structured Query Language) is used to interact with **relational databases**. 22 | 23 | --- 24 | 25 | ## Docker 26 | 27 | **Docker** is a platform for developing, shipping, and running applications. It allows you to package your application and its dependencies into a container. A container is a standard unit of software that packages up code and all its dependencies so the application runs quickly and reliably from one computing environment to another. We are going to use **Docker** to run a **PostgreSQL** container. 28 | 29 | > **Resource:** 30 | 31 | --- 32 | 33 | ### Getting Started 34 | 35 | To get started, open **Docker Desktop**, and a terminal and run the following. 36 | 37 | ```bash 38 | docker run --name id607001-db-dev -e POSTGRES_PASSWORD=HelloWorld123 -p 5432:5432 -d postgres 39 | ``` 40 | 41 | What does each do? 42 | 43 | - `docker run`: This command creates a new container. 44 | - `--name id607001-db-dev`: This command names the container **id607001-db-dev**. 45 | - `-e POSTGRES_PASSWORD=HelloWorld123`: This command sets the **PostgreSQL** password to **HelloWorld123**. 46 | - `-p 5432:5432`: This command maps the container's port **5432** to the host's port **5432**. 47 | - `-d postgres`: This command uses the **PostgreSQL** image to create the container. 48 | 49 | To check if the container is running, run the following command. 50 | 51 | ```bash 52 | docker ps 53 | ``` 54 | 55 | To stop the container, run the following command. 56 | 57 | ```bash 58 | docker stop id607001-db-dev 59 | ``` 60 | 61 | To remove the container, run the following command. 62 | 63 | ```bash 64 | docker rm id607001-db-dev 65 | ``` 66 | 67 | --- 68 | 69 | ## Object-Relational Mapper (ORM) 70 | 71 | An **Object-Relational Mapper (ORM)** is a layer that sits between the database and the application. It maps the relational database to objects in the application. It allows developers to work with objects instead of tables and **SQL**. 72 | 73 | --- 74 | 75 | ### Setup 76 | 77 | The **ORM** we are going to use is **Prisma** which is an open-source **ORM** for **Node.js** and **TypeScript**. It supports **PostgreSQL**, **MySQL**, **SQLite**, and **SQL Server**. 78 | 79 | To get started, open a terminal and run the following. 80 | 81 | ```bash 82 | npm install @prisma/client 83 | npm install prisma --save-dev 84 | npx prisma init 85 | ``` 86 | 87 | > **Note:** You only need to run these once. 88 | 89 | What does each do? 90 | 91 | - `npm install @prisma/client`: Installs the **Prisma Client** package. The **Prisma Client** is used to interact with the database. 92 | - `npm install prisma --save-dev`: Installs the **Prisma** package. The **Prisma** package is used to create and apply migrations. 93 | - `npx prisma init`: Initialises **Prisma** in your project. It creates the `.env` file and the `prisma` directory. 94 | 95 | The `.env` file is used to store environment variables. For example, database connection string. The `prisma` directory is used to store **Prisma** configuration files. For example, `schema.prisma`. 96 | 97 | --- 98 | 99 | ### .env File 100 | 101 | A **.env** file is used to store environment variables. It is used to store sensitive information. For example, database connection string. 102 | 103 | In the `.env` file, you will see the following code. 104 | 105 | ```bash 106 | DATABASE_URL="postgresql://johndoe:randompassword@localhost:5432/mydb?schema=public" 107 | ``` 108 | 109 | Update the `DATABASE_URL` environment variable's value with the following code. 110 | 111 | ```bash 112 | APP_ENV=development 113 | DATABASE_URL="postgresql://postgres:HelloWorld123@localhost:5432/postgres" 114 | ``` 115 | 116 | > **Note:** The `.env` file is not committed to **Git**. The **Node** `.gitignore` file ignores the `.env` file. 117 | 118 | --- 119 | 120 | ### .env.example File 121 | 122 | The `.env.example` file is used to provide an example of the `.env` file. It is committed to **Git**. It is used to show other developers what environment variables are required. It is also used to provide default values. Here is an example of the `.env.example` file. 123 | 124 | ```bash 125 | APP_ENV=development 126 | DATABASE_URL= 127 | ``` 128 | 129 | > **Note:** The `.env.example` file is committed to **Git**. The **Node** `.gitignore` file does not ignore the `.env.example` file. 130 | 131 | --- 132 | 133 | ### Schema Prisma File 134 | 135 | You will see the following code in the `schema.prisma` file. 136 | 137 | ```javascript 138 | generator client { 139 | provider = "prisma-client-js" 140 | } 141 | 142 | datasource db { 143 | provider = "postgresql" 144 | url = env("DATABASE_URL") 145 | } 146 | ``` 147 | 148 | The `generator` block is used to specify the **Prisma Client** provider. The **Prisma Client** is used to interact with the database. 149 | 150 | > **Resource:** 151 | 152 | The `datasource` block is used to specify the database provider and URL. The `url` value is retrieved from the `DATABASE_URL` environment variable. 153 | 154 | > **Resource:** 155 | 156 | --- 157 | 158 | ### Model 159 | 160 | Under `datasource db` block, add the following code. 161 | 162 | ```javascript 163 | model Institution { 164 | id String @id @default(uuid()) 165 | name String @unique 166 | region String 167 | country String 168 | createdAt DateTime @default(now()) 169 | updatedAt DateTime @updatedAt 170 | } 171 | ``` 172 | 173 | - A `model` is used to define a database table. In this case, we are defining an `Institution` table. 174 | - The `@id` directive is used to specify the primary key. 175 | - The `@default` directive is used to specify the default value. 176 | - `uuid()` is a function that generates a **UUID** (Universally Unique Identifier). 177 | - The `@unique` directive is used to specify that the value should be unique. 178 | - The `@default(now())` directive is used to specify that the value should be the current date and time. 179 | - The `@updatedAt` directive is used to specify that the value should be updated when the row in the table is updated. 180 | 181 | > **Resource:** 182 | 183 | --- 184 | 185 | ### Create and Apply a Migration 186 | 187 | A **migration** is a file that contains the **SQL** statements to create, update, or delete database tables. It is used to keep the database schema in sync with the application. 188 | 189 | To create and apply a migration, run the following command. 190 | 191 | ```bash 192 | npx prisma migrate dev 193 | ``` 194 | 195 | You will be prompted to enter a name for the migration. Do not enter anything and press the `Enter` key. The new migration is in the `prisma/migrations` directory. You are encouraged to read the migration file. You should see some **SQL** statements. 196 | 197 | > **Note:** When you make a change to the `schema.prisma` file, you need to create a new migration and apply it. 198 | 199 | --- 200 | 201 | ### Reset the Database 202 | 203 | To reset the database, run the following command. 204 | 205 | ```bash 206 | npx prisma migrate reset --force 207 | ``` 208 | 209 | > **Note:** This command will delete all the data in the database. Use it with caution. 210 | 211 | --- 212 | 213 | ### Package JSON File 214 | 215 | You will often run the `npx prisma migrate dev` and `npx prisma migrate reset --force` commands. To make it easier, add the following scripts to the `package.json` file. 216 | 217 | ```json 218 | "prisma:migrate": "npx prisma migrate dev", 219 | "prisma:reset": "npx prisma migrate reset --force" 220 | ``` 221 | 222 | Your `scripts` block should look like this. 223 | 224 | ```json 225 | "scripts": { 226 | "test": "echo \"Error: no test specified\" && exit 1", 227 | "dev": "nodemon app.js", 228 | "prisma:migrate": "npx prisma migrate dev", 229 | "prisma:reset": "npx prisma migrate reset --force" 230 | }, 231 | ``` 232 | 233 | --- 234 | 235 | ### Prisma Client 236 | 237 | In the `prisma` directory, create a new file called `client.js`. Add the following code. 238 | 239 | ```javascript 240 | import { PrismaClient } from "@prisma/client"; 241 | 242 | const prisma = new PrismaClient(); 243 | 244 | export default prisma; 245 | ``` 246 | 247 | ### Institution Controller 248 | 249 | In the `controllers` directory, create a new file called `institution.js`. Add the following code. 250 | 251 | ```javascript 252 | import prisma from "../prisma/client.js"; 253 | ``` 254 | 255 | To create an institution, use the `prisma.institution.create` function. 256 | 257 | ```js 258 | // Add the following code under import prisma from "../prisma/client.js"; 259 | const createInstitution = async (req, res) => { 260 | // Try/catch blocks are used to handle exceptions 261 | try { 262 | // Create a new institution 263 | await prisma.institution.create({ 264 | // Data to be inserted 265 | data: { 266 | name: req.body.name, 267 | region: req.body.region, 268 | country: req.body.country, 269 | }, 270 | }); 271 | 272 | // Get all institutions from the institution table 273 | const newInstitutions = await prisma.institution.findMany(); 274 | 275 | // Send a JSON response 276 | return res.status(201).json({ 277 | message: "Institution successfully created", 278 | data: newInstitutions, 279 | }); 280 | } catch (err) { 281 | return res.status(500).json({ 282 | message: err.message, 283 | }); 284 | } 285 | }; 286 | ``` 287 | 288 | To get all institutions, use the `prisma.institution.findMany` function. 289 | 290 | ```js 291 | // Add the following code under the createInstitution function 292 | const getInstitutions = async (req, res) => { 293 | try { 294 | const institutions = await prisma.institution.findMany(); 295 | 296 | // Check if there are no institutions 297 | if (!institutions) { 298 | return res.status(404).json({ message: "No institutions found" }); 299 | } 300 | 301 | return res.status(200).json({ 302 | data: institutions, 303 | }); 304 | } catch (err) { 305 | return res.status(500).json({ 306 | message: err.message, 307 | }); 308 | } 309 | }; 310 | ``` 311 | 312 | To get an institution, use the `prisma.institution.findUnique` function. 313 | 314 | ```js 315 | // Add the following code under the getInstitutions function 316 | const getInstitution = async (req, res) => { 317 | try { 318 | const institution = await prisma.institution.findUnique({ 319 | where: { id: req.params.id }, 320 | }); 321 | 322 | // Check if there is no institution 323 | if (!institution) { 324 | return res.status(404).json({ 325 | message: `No institution with the id: ${req.params.id} found`, 326 | }); 327 | } 328 | 329 | return res.status(200).json({ 330 | data: institution, 331 | }); 332 | } catch (err) { 333 | return res.status(500).json({ 334 | message: err.message, 335 | }); 336 | } 337 | }; 338 | ``` 339 | 340 | To update an institution, use the `prisma.institution.update` function. 341 | 342 | ```js 343 | // Add the following code under the getInstitution function 344 | const updateInstitution = async (req, res) => { 345 | try { 346 | // Find the institution by id 347 | let institution = await prisma.institution.findUnique({ 348 | where: { id: req.params.id }, 349 | }); 350 | 351 | // Check if there is no institution 352 | if (!institution) { 353 | return res.status(404).json({ 354 | message: `No institution with the id: ${req.params.id} found`, 355 | }); 356 | } 357 | 358 | // Update the institution 359 | institution = await prisma.institution.update({ 360 | where: { id: req.params.id }, 361 | data: { 362 | // Data to be updated 363 | name: req.body.name, 364 | region: req.body.region, 365 | country: req.body.country, 366 | }, 367 | }); 368 | 369 | return res.status(200).json({ 370 | message: `Institution with the id: ${req.params.id} successfully updated`, 371 | data: institution, 372 | }); 373 | } catch (err) { 374 | return res.status(500).json({ 375 | message: err.message, 376 | }); 377 | } 378 | }; 379 | ``` 380 | 381 | To delete an institution, use the `prisma.institution.delete` function. 382 | 383 | ```js 384 | // Add the following code under the updateInstitution function 385 | const deleteInstitution = async (req, res) => { 386 | try { 387 | const institution = await prisma.institution.findUnique({ 388 | where: { id: req.params.id }, 389 | }); 390 | 391 | if (!institution) { 392 | return res.status(404).json({ 393 | message: `No institution with the id: ${req.params.id} found`, 394 | }); 395 | } 396 | 397 | await prisma.institution.delete({ 398 | where: { id: req.params.id }, 399 | }); 400 | 401 | return res.json({ 402 | message: `Institution with the id: ${req.params.id} successfully deleted`, 403 | }); 404 | } catch (err) { 405 | return res.status(500).json({ 406 | message: err.message, 407 | }); 408 | } 409 | }; 410 | ``` 411 | 412 | To use the functions in the `institution.js` file, export them. 413 | 414 | ```js 415 | // Add the following code under the deleteInstitution function 416 | export { 417 | createInstitution, 418 | getInstitutions, 419 | getInstitution, 420 | updateInstitution, 421 | deleteInstitution, 422 | }; 423 | ``` 424 | 425 | --- 426 | 427 | ### Institution Router 428 | 429 | In the `routes` directory, create a new file called `institution.js`. Add the following code. 430 | 431 | ```javascript 432 | import express from "express"; 433 | 434 | import { 435 | createInstitution, 436 | getInstitutions, 437 | getInstitution, 438 | updateInstitution, 439 | deleteInstitution, 440 | } from "../controllers/institution.js"; 441 | 442 | const router = express.Router(); 443 | 444 | router.post("/", createInstitution); 445 | router.get("/", getInstitutions); 446 | router.get("/:id", getInstitution); 447 | router.put("/:id", updateInstitution); 448 | router.delete("/:id", deleteInstitution); 449 | 450 | // Note: You can chain the routes like this - 451 | // router.route("/").post(createInstitution).get(getInstitutions); 452 | 453 | export default router; 454 | ``` 455 | 456 | `:id` is a route parameter. It is used to retrieve the id from the request URL. For example, if the request URL is , the `:id` value will be `uuid`. 457 | 458 | --- 459 | 460 | ### Main File 461 | 462 | In the `app.js` file, add the following code. 463 | 464 | ```javascript 465 | // This should be declared under - import indexRoutes from "./routes/index.js"; 466 | import institutionRoutes from "./routes/institution.js"; 467 | 468 | // This should be declared above app.use("/", indexRoutes); 469 | app.use(express.urlencoded({ extended: false })); // To parse the incoming requests with urlencoded payloads. For example, form data 470 | 471 | // This should be declared under - app.use(urlencoded({ extended: false })); 472 | app.use(express.json()); // To parse the incoming requests with JSON payloads. For example, REST API requests 473 | 474 | // This should be declared under - app.use("/", indexRoutes); 475 | app.use("/api/institutions", institutionRoutes); 476 | ``` 477 | 478 | > **Note:** We are using `/api/institutions` as the base URL for all the institution routes. For example, `/api/institutions`, `/api/institutions/uuid`, etc. Also, your resources should be pluralised. For example, `/api/institutions` instead of `/api/institution`. 479 | 480 | --- 481 | 482 | ## JSDoc 483 | 484 | **JSDoc** is an API documentation generator for **JavaScript**. **JSDoc** comments are written in a specific syntax to document the code. The **JSDoc** comments are then parsed and converted into HTML documentation. We will not convert the **JSDoc** comments into HTML documentation. However, it is good information to know. 485 | 486 | --- 487 | 488 | ### Getting Started 489 | 490 | At the top of each file, add the following code. 491 | 492 | ```javascript 493 | /** 494 | * @file 495 | * @author 496 | */ 497 | ``` 498 | 499 | For example, in the `controllers/institution.js` file. 500 | 501 | ```javascript 502 | /** 503 | * @file Manages all operations related to institutions 504 | * @author John Doe 505 | */ 506 | ``` 507 | 508 | > **Note:** `@fileoverview` or `@overview` can also be used instead of `@file`. 509 | 510 | How do you comment a **function**? 511 | 512 | ```javascript 513 | /** 514 | * @description This function creates a new institution 515 | * @param {object} req - The request object 516 | * @param {object} res - The response object 517 | * @returns {object} - The response object 518 | */ 519 | const createInstitution = async (req, res) => { 520 | try { 521 | await prisma.institution.create({ 522 | data: { 523 | name: req.body.name, 524 | region: req.body.region, 525 | country: req.body.country, 526 | }, 527 | }); 528 | 529 | const newInstitutions = await prisma.institution.findMany(); 530 | 531 | return res.status(201).json({ 532 | message: "Institution successfully created", 533 | data: newInstitutions, 534 | }); 535 | } catch (err) { 536 | return res.status(500).json({ 537 | message: err.message, 538 | }); 539 | } 540 | }; 541 | ``` 542 | 543 | > **Note:** Do not use **JSDoc** for in-line comments. Use normal JavaScript comments. 544 | 545 | --- 546 | 547 | ## Swagger 548 | 549 | **Swagger** is a set of open-source tools built around the **OpenAPI Specification** that can help you design, build, document, and consume REST APIs. 550 | 551 | --- 552 | 553 | ### Setup 554 | 555 | To get started, open a terminal and run the following. 556 | 557 | ```bash 558 | npm install swagger-ui-express swagger-jsdoc 559 | ``` 560 | 561 | --- 562 | 563 | ### Main File 564 | 565 | In the `app.js` file, add the following code. 566 | 567 | ```javascript 568 | // This should be declared under - import express from "express"; 569 | import swaggerJSDoc from "swagger-jsdoc"; 570 | 571 | // This should be declared under - import swaggerJSDoc from "swagger-jsdoc"; 572 | import swaggerUi from "swagger-ui-express"; 573 | 574 | // This should be declared under - app.use(express.json()); 575 | const swaggerOptions = { 576 | definition: { 577 | openapi: "3.0.0", 578 | info: { 579 | title: "Student Management System API", 580 | version: "1.0.0", 581 | description: "A student management system API", 582 | contact: { 583 | name: "Grayson Orr", 584 | }, 585 | }, 586 | servers: [ 587 | { 588 | url: "http://localhost:3000", 589 | }, 590 | ], 591 | }, 592 | apis: ["./routes/*.js"], 593 | }; 594 | 595 | // This should be declared under - const swaggerOptions = { ... }; 596 | const swaggerDocs = swaggerJSDoc(swaggerOptions); 597 | 598 | // This should be declared under - app.use("/api/institutions", institutionRoutes); 599 | app.use("/api-docs", swaggerUi.serve, swaggerUi.setup(swaggerDocs)); 600 | ``` 601 | 602 | --- 603 | 604 | ### Institution Router 605 | 606 | In the `routes/institution.js` file, update the code as follows. 607 | 608 | ```javascript 609 | import express from "express"; 610 | 611 | import { 612 | createInstitution, 613 | getInstitutions, 614 | getInstitution, 615 | updateInstitution, 616 | deleteInstitution, 617 | } from "../controllers/institution.js"; 618 | 619 | const router = express.Router(); 620 | 621 | /** 622 | * @swagger 623 | * components: 624 | * schemas: 625 | * Institution: 626 | * type: object 627 | * properties: 628 | * id: 629 | * type: string 630 | * format: uuid 631 | * example: "123e4567-e89b-12d3-a456-426614174000" 632 | * name: 633 | * type: string 634 | * example: "Institution Name" 635 | * region: 636 | * type: string 637 | * example: "Region Name" 638 | * country: 639 | * type: string 640 | * example: "Country Name" 641 | * createdAt: 642 | * type: string 643 | * format: date-time 644 | * example: "2024-07-14T12:34:56Z" 645 | * updatedAt: 646 | * type: string 647 | * format: date-time 648 | * example: "2024-07-14T12:34:56Z" 649 | */ 650 | 651 | /** 652 | * @swagger 653 | * /api/institutions: 654 | * post: 655 | * summary: Create a new institution 656 | * tags: 657 | * - Institution 658 | * requestBody: 659 | * required: true 660 | * content: 661 | * application/json: 662 | * schema: 663 | * $ref: '#/components/schemas/Institution' 664 | * responses: 665 | * '201': 666 | * description: Institution successfully created 667 | * content: 668 | * application/json: 669 | * schema: 670 | * type: object 671 | * properties: 672 | * message: 673 | * type: string 674 | * example: "Institution successfully created" 675 | * data: 676 | * type: array 677 | * items: 678 | * $ref: '#/components/schemas/Institution' 679 | * '400': 680 | * description: Institution with the same name already exists 681 | * content: 682 | * application/json: 683 | * schema: 684 | * type: object 685 | * properties: 686 | * message: 687 | * type: string 688 | * example: "Institution with the same name already exists" 689 | * '500': 690 | * description: Internal server error 691 | * content: 692 | * application/json: 693 | * schema: 694 | * type: object 695 | * properties: 696 | * message: 697 | * type: string 698 | * example: "An unexpected error occurred" 699 | */ 700 | router.post("/", createInstitution); 701 | 702 | /** 703 | * @swagger 704 | * /api/institutions: 705 | * get: 706 | * summary: Get all institutions 707 | * tags: 708 | * - Institution 709 | * responses: 710 | * '200': 711 | * description: Success 712 | * content: 713 | * application/json: 714 | * schema: 715 | * type: object 716 | * properties: 717 | * data: 718 | * type: array 719 | * items: 720 | * $ref: '#/components/schemas/Institution' 721 | * '404': 722 | * description: No institutions found 723 | * content: 724 | * application/json: 725 | * schema: 726 | * type: object 727 | * properties: 728 | * message: 729 | * type: string 730 | * example: "No institutions found" 731 | * '500': 732 | * description: Internal server error 733 | * content: 734 | * application/json: 735 | * schema: 736 | * type: object 737 | * properties: 738 | * message: 739 | * type: string 740 | * example: "An unexpected error occurred" 741 | */ 742 | router.get("/", getInstitutions); 743 | 744 | /** 745 | * @swagger 746 | * /api/institutions/{id}: 747 | * get: 748 | * summary: Get an institution by id 749 | * tags: 750 | * - Institution 751 | * parameters: 752 | * - in: path 753 | * name: id 754 | * required: true 755 | * schema: 756 | * type: string 757 | * description: The institution id 758 | * responses: 759 | * '200': 760 | * description: Success 761 | * content: 762 | * application/json: 763 | * schema: 764 | * $ref: '#/components/schemas/Institution' 765 | * '404': 766 | * description: No institution found with the provided id 767 | * content: 768 | * application/json: 769 | * schema: 770 | * type: object 771 | * properties: 772 | * message: 773 | * type: string 774 | * example: "No institution with the id: {id} found" 775 | * '500': 776 | * description: Internal server error 777 | * content: 778 | * application/json: 779 | * schema: 780 | * type: object 781 | * properties: 782 | * message: 783 | * type: string 784 | * example: "An unexpected error occurred" 785 | */ 786 | router.get("/:id", getInstitution); 787 | 788 | /** 789 | * @swagger 790 | * /api/institutions/{id}: 791 | * put: 792 | * summary: Update an institution by id 793 | * tags: 794 | * - Institution 795 | * parameters: 796 | * - in: path 797 | * name: id 798 | * required: true 799 | * schema: 800 | * type: string 801 | * description: The institution id 802 | * requestBody: 803 | * required: true 804 | * content: 805 | * application/json: 806 | * schema: 807 | * $ref: '#/components/schemas/Institution' 808 | * responses: 809 | * '200': 810 | * description: Institution successfully updated 811 | * content: 812 | * application/json: 813 | * schema: 814 | * type: object 815 | * properties: 816 | * message: 817 | * type: string 818 | * example: "Institution with the id: {id} successfully updated" 819 | * data: 820 | * $ref: '#/components/schemas/Institution' 821 | * '404': 822 | * description: No institution found with the provided id 823 | * content: 824 | * application/json: 825 | * schema: 826 | * type: object 827 | * properties: 828 | * message: 829 | * type: string 830 | * example: "No institution with the id: {id} found" 831 | * '500': 832 | * description: Internal server error 833 | * content: 834 | * application/json: 835 | * schema: 836 | * type: object 837 | * properties: 838 | * message: 839 | * type: string 840 | * example: "An unexpected error occurred" 841 | */ 842 | router.put("/:id", updateInstitution); 843 | 844 | /** 845 | * @swagger 846 | * /api/institutions/{id}: 847 | * delete: 848 | * summary: Delete an institution by id 849 | * tags: 850 | * - Institution 851 | * parameters: 852 | * - in: path 853 | * name: id 854 | * required: true 855 | * schema: 856 | * type: string 857 | * description: The institution id 858 | * responses: 859 | * '200': 860 | * description: Institution successfully deleted 861 | * content: 862 | * application/json: 863 | * schema: 864 | * type: object 865 | * properties: 866 | * message: 867 | * type: string 868 | * example: "Institution with the id: {id} successfully deleted" 869 | * '404': 870 | * description: No institution found with the provided id 871 | * content: 872 | * application/json: 873 | * schema: 874 | * type: object 875 | * properties: 876 | * message: 877 | * type: string 878 | * example: "No institution with the id: {id} found" 879 | * '500': 880 | * description: Internal server error 881 | * content: 882 | * application/json: 883 | * schema: 884 | * type: object 885 | * properties: 886 | * message: 887 | * type: string 888 | * example: "An unexpected error occurred" 889 | */ 890 | router.delete("/:id", deleteInstitution); 891 | 892 | export default router; 893 | ``` 894 | 895 | There can be quite a lot of properties in a **Swagger** comment. Here are some of the properties. 896 | 897 | - `@swagger`: This is used to specify the **OpenAPI Specification** version. 898 | - `components`: This is used to define reusable components. 899 | - `schemas`: This is used to define the data model. 900 | - `summary`: This is a short summary of the operation. 901 | - `tags`: This is used to group operations together. 902 | - `requestBody`: This is used to specify the request body. 903 | - `parameters`: This is used to specify the parameters. 904 | - `responses`: This is used to specify the responses. 905 | 906 | > **Note:** It is tedious to write **Swagger** comments. However, it is good practice to write them. It will help you and other developers understand the API. 907 | 908 | --- 909 | 910 | ### Swagger Documentation 911 | 912 | To test the **Swagger** documentation, run the application and go to . You should see the following. 913 | 914 | ![](<../resources (ignore)/img/03/swagger-1.png>) 915 | 916 | --- 917 | 918 | ### POST Request Example 919 | 920 | Click on the **Try it out** button. 921 | 922 | ![](<../resources (ignore)/img/03/swagger-2.png>) 923 | 924 | Add the following code in the **Request body**. 925 | 926 | ```json 927 | { 928 | "name": "Otago Polytechnic", 929 | "region": "Otago", 930 | "country": "New Zealand" 931 | } 932 | ``` 933 | 934 | Then click on the **Execute** button. 935 | 936 | ![](<../resources (ignore)/img/03/swagger-3.png>) 937 | 938 | In the **Responses** section, you should see the following. 939 | 940 | ![](<../resources (ignore)/img/03/swagger-4.png>) 941 | 942 | --- 943 | 944 | ### GET All Request Example 945 | 946 | Click on the **Try it out** button. 947 | 948 | ![](<../resources (ignore)/img/03/swagger-5.png>) 949 | 950 | Click on the **Execute** button. 951 | 952 | ![](<../resources (ignore)/img/03/swagger-6.png>) 953 | 954 | In the **Responses** section, you should see the following. 955 | 956 | ![](<../resources (ignore)/img/03/swagger-7.png>) 957 | 958 | --- 959 | 960 | ### GET One Request Example 961 | 962 | Click on the **Try it out** button. 963 | 964 | ![](<../resources (ignore)/img/03/swagger-8.png>) 965 | 966 | In the **id** field, enter the **id** of the institution you want to retrieve. Click on the **Execute** button. 967 | 968 | ![](<../resources (ignore)/img/03/swagger-9.png>) 969 | 970 | In the **Responses** section, you should see the following. 971 | 972 | ![](<../resources (ignore)/img/03/swagger-10.png>) 973 | 974 | What happens if you enter an **id** that does not exist? 975 | 976 | --- 977 | 978 | ### PUT Request Example 979 | 980 | Click on the **Try it out** button. 981 | 982 | ![](<../resources (ignore)/img/03/swagger-11.png>) 983 | 984 | In the **id** field, enter the **id** of the institution you want to update. Add the following code in the **Request body**. 985 | 986 | ```json 987 | { 988 | "name": "Otago University" 989 | } 990 | ``` 991 | 992 | > **Note:** You only need to enter the fields you want to update. 993 | 994 | Click on the **Execute** button. 995 | 996 | ![](<../resources (ignore)/img/03/swagger-12.png>) 997 | 998 | In the **Responses** section, you should see the following. 999 | 1000 | ![](<../resources (ignore)/img/03/swagger-13.png>) 1001 | 1002 | --- 1003 | 1004 | ### DELETE Request Example 1005 | 1006 | Click on the **Try it out** button. 1007 | 1008 | ![](<../resources (ignore)/img/03/swagger-14.png>) 1009 | 1010 | In the **id** field, enter the **id** of the institution you want to delete. Click on the **Execute** button. 1011 | 1012 | ![](<../resources (ignore)/img/03/swagger-15.png>) 1013 | 1014 | In the **Responses** section, you should see the following. 1015 | 1016 | ![](<../resources (ignore)/img/03/swagger-16.png>) 1017 | 1018 | --- 1019 | 1020 | ## Formative Assessment 1021 | 1022 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 1023 | 1024 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 1025 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 1026 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 1027 | --- 1028 | 1029 | ### Task One 1030 | 1031 | Implement the code examples above. 1032 | 1033 | --- 1034 | 1035 | ### Task Two - Optional Fields (Independent Research) 1036 | 1037 | In the `schema.prisma` file, update the `Institution` model to include optional fields for `phoneNumber` and `website`. 1038 | 1039 | --- 1040 | 1041 | ### Task Three - Prisma Studio (Independent Research) 1042 | 1043 | **Prisma Studio** is a visual editor for your database. It allows you to view and edit your data. Create a new script in the `package.json` file called `prisma:studio`. This script should open **Prisma Studio** in the browser. 1044 | 1045 | > **Resource:** 1046 | 1047 | --- 1048 | 1049 | ## Next Class 1050 | 1051 | Link to the next class: [Week 04](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-04-api-versioning-relationships-repository-pattern.md) 1052 | -------------------------------------------------------------------------------- /lecture-notes/week-04-api-versioning-relationships-repository-pattern.md: -------------------------------------------------------------------------------- 1 | # Week 04 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 03](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-03-render-postgresql-jsdocs-swagger.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-04-formative-assessment** from **week-03-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## API Versioning 18 | 19 | API versioning is the process of versioning your API. It is important to version your API because it allows you to make changes to your API without breaking the existing clients. There are different ways to version your API. Some of the common ways are: 20 | 21 | 1. **URL Versioning:** In URL versioning, the version number is included in the URL. For example, `https://api.example.com/v1/products`. 22 | 23 | 2. **Query Parameter Versioning:** In query parameter versioning, the version number is included as a query parameter. For example, `https://api.example.com/products?version=1`. 24 | 25 | 3. **Header Versioning:** In header versioning, the version number is included in the header. For example, `Accept: application/vnd.example.v1+json`. 26 | 27 | 4. **Media Type Versioning:** In media type versioning, the version number is included in the media type. For example, `Accept: application/vnd.example.v1+json`. 28 | 29 | --- 30 | 31 | ### Controllers and Routes - Refactor 32 | 33 | In the `controllers` and `routes` directories, create a new directory called `v1`. Move the `institution.js` files to the `v1` directories. Update the import path in the `routes/v1/institution.js` file. 34 | 35 | ```javascript 36 | import { 37 | createInstitution, 38 | getInstitutions, 39 | getInstitution, 40 | updateInstitution, 41 | deleteInstitution, 42 | } from "../../controllers/v1/institution.js"; 43 | ``` 44 | 45 | Also, update the **Swagger** comments. For example, `/api/v1/institutions:` instead of `/api/institutions:`. 46 | 47 | --- 48 | 49 | ### Main File - Refactor 50 | 51 | In the `app.js` file, update the import path for the `routes/v1/institution.js` file. 52 | 53 | ```javascript 54 | // This should be declared under - import indexRoutes from "./routes/index.js"; 55 | import institutionRoutes from "./routes/v1/institution.js"; 56 | ``` 57 | 58 | Also, update the following code. 59 | 60 | ```javascript 61 | // This should be declared under - app.use(express.json()); 62 | const swaggerOptions = { 63 | // ... 64 | apis: ["./routes/v1/*.js"], 65 | }; 66 | 67 | // This should be declared under - app.use("/", indexRoutes); 68 | app.use(`/api/v1/institutions`, institutionRoutes); 69 | ``` 70 | 71 | --- 72 | 73 | ### API Versioning Example 74 | 75 | Run the application and go to . You should see the following. 76 | 77 | ![](<../resources (ignore)/img/04/swagger-1.png>) 78 | 79 | --- 80 | 81 | ## Content Negotiation 82 | 83 | Content negotiation is the process of selecting the best representation of a resource based on the client's preferences. There are different ways to perform content negotiation. Some of the common ways are: 84 | 85 | 1. **Accept Header:** In the Accept header, the client specifies the media types it can accept. For example, `Accept: application/json`. 86 | 87 | 2. **Content-Type Header:** In the Content-Type header, the client specifies the media type of the request body. For example, `Content-Type: application/json`. 88 | 89 | 3. **Query Parameter:** In the query parameter, the client specifies the media type. For example, `https://api.example.com/products?format=json`. 90 | 91 | In this class, we will use the **Accept Header** to perform content negotiation. 92 | 93 | --- 94 | 95 | ### Middleware 96 | 97 | **Middleware** is a function that has access to the request object (`req`), the response object (`res`), and the next middleware function in the application's request-response cycle. Middleware functions can perform the following tasks: 98 | 99 | - Execute any code. 100 | - Make changes to the request and the response objects. 101 | - End the request-response cycle. 102 | - Call the next middleware function in the stack. 103 | 104 | In the root directory, create a new directory called `middleware`. In the `middleware` directory, create a new file called `utils.js`. Add the following code. 105 | 106 | ```javascript 107 | const isContentTypeApplicationJSON = (req, res, next) => { 108 | // Check if the request method is POST or PUT 109 | if (req.method === "POST" || req.method === "PUT") { 110 | // Check if the Content-Type header is application/json 111 | const contentType = req.headers["content-type"]; 112 | if (!contentType || contentType !== "application/json") { 113 | return res.status(409).json({ 114 | error: { 115 | message: "Content-Type must be application/json", 116 | }, 117 | }); 118 | } 119 | } 120 | next(); 121 | }; 122 | 123 | export { isContentTypeApplicationJSON }; 124 | ``` 125 | 126 | --- 127 | 128 | ### Main File 129 | 130 | In the `app.js` file, add the following code. 131 | 132 | ```javascript 133 | // This should be declared under - import institutionRoutes from "./routes/v1/institution.js"; 134 | import { isContentTypeApplicationJSON } from "./middleware/utils.js"; 135 | 136 | // This should be declared under - const swaggerDocs = swaggerJSDoc(swaggerOptions); 137 | app.use(isContentTypeApplicationJSON); 138 | ``` 139 | 140 | --- 141 | 142 | ### Institution Router 143 | 144 | In the `routes/v1/institution.js` file, update the following code. 145 | 146 | ```javascript 147 | /** 148 | * @swagger 149 | * /api/v1/institutions: 150 | * post: 151 | * summary: Create a new institution 152 | * tags: 153 | * - Institution 154 | * requestBody: 155 | * required: true 156 | * content: 157 | * text/plain: 158 | * schema: 159 | * $ref: '#/components/schemas/Institution' 160 | ``` 161 | 162 | > **Note:** The `content` property has been updated to `text/plain`. It is for testing purposes only. Ensure that you update it to `application/json` after testing. 163 | 164 | --- 165 | 166 | ### Content Negotiation Example 167 | 168 | Run the application and go to . Click on the **POST** request for `/api/v1/institutions`. Click on the **Try it out** button. Add the following code in the **Request body**. 169 | 170 | ```json 171 | { 172 | "name": "Otago Polytechnic", 173 | "region": "Otago", 174 | "country": "New Zealand" 175 | } 176 | ``` 177 | 178 | Click on the **Execute** button. 179 | 180 | ![](<../resources (ignore)/img/04/swagger-2.png>) 181 | 182 | In the **Responses** section, you should see the following. 183 | 184 | ![](<../resources (ignore)/img/04/swagger-3.png>) 185 | 186 | --- 187 | 188 | ## Relationships 189 | 190 | In **Prisma**, we can define different types of relationships between models. Here are three types you will encounter most often. 191 | 192 | - **One-to-one:** A single model instance is associated with a single instance of another model. 193 | - **One-to-many:** A single model instance is associated with multiple instances of another model. 194 | - **Many-to-many:** Multiple instances of a model are associated with multiple instances of another model. 195 | 196 | ### Prisma Schema File 197 | 198 | In the `schema.prisma` file, add the following code under the `model Institution` block. 199 | 200 | ```prisma 201 | model Department { 202 | id String @id @default(uuid()) 203 | name String 204 | institutionId String 205 | institution Institution @relation(fields: [institutionId], references: [id], onDelete: Cascade, onUpdate: Cascade) 206 | createdAt DateTime @default(now()) 207 | updatedAt DateTime @updatedAt 208 | } 209 | ``` 210 | 211 | Also, update the `model Institution` block. 212 | 213 | ```prisma 214 | model Institution { 215 | id String @id @default(uuid()) 216 | name String 217 | region String 218 | country String 219 | departments Department[] 220 | createdAt DateTime @default(now()) 221 | updatedAt DateTime @updatedAt 222 | } 223 | ``` 224 | 225 | > What type of relationship is this? This is a **one-to-many** relationship. A single institution can have multiple departments. 226 | 227 | --- 228 | 229 | ### Department Controller and Router 230 | 231 | Much like the `institution.js` files, create a new `department.js` file in the `controllers/v1` and `routes/v1` directories. The code in these files should be similar to the `institution.js` files. 232 | 233 | --- 234 | 235 | ### Main File 236 | 237 | In the `app.js` file, add the following code. 238 | 239 | ```javascript 240 | // This should be declared under - import institutionRoutes from "./routes/v1/institution.js"; 241 | import departmentRoutes from "./routes/v1/department.js"; 242 | 243 | // This should be declared under - app.use("/api/v1/institutions", institutionRoutes); 244 | app.use("/api/v1/departments", departmentRoutes); 245 | ``` 246 | 247 | --- 248 | 249 | ### Swagger Documentation 250 | 251 | Run the application and go to . You should see the following. 252 | 253 | ![](<../resources (ignore)/img/04/swagger-4.png>) 254 | 255 | --- 256 | 257 | ### POST Request Example 258 | 259 | Click on the **Try it out** button. 260 | 261 | ![](<../resources (ignore)/img/04/swagger-5.png>) 262 | 263 | Add the following code in the **Request body**. 264 | 265 | ```json 266 | { 267 | "name": "Information Technology", 268 | "institutionId": "" 269 | } 270 | ``` 271 | 272 | Then click on the **Execute** button. 273 | 274 | ![](<../resources (ignore)/img/04/swagger-6.png>) 275 | 276 | In the **Responses** section, you should see the following. 277 | 278 | ![](<../resources (ignore)/img/04/swagger-7.png>) 279 | 280 | --- 281 | 282 | ## Repository Pattern 283 | 284 | The repository pattern is a design pattern that separates the data access logic from the business logic. It is a common pattern used in modern web applications. The repository pattern has the following benefits: 285 | 286 | - **Separation of Concerns:** The repository pattern separates the data access logic from the business logic. This makes the code easier to maintain and test. 287 | - **Testability:** The repository pattern makes it easier to test the data access logic and the business logic separately. For example, you can write unit tests for the data access logic without having to set up a database. 288 | - **Flexibility:** The repository pattern makes it easier to switch between different data access technologies. For example, you can switch from a SQL database to a NoSQL database without changing the business logic. 289 | 290 | --- 291 | 292 | ### Institution Repository Class 293 | 294 | In the root directory, create a new directory called `repositories`. In the `repositories` directory, create a new file called `institution.js`. Add the following code. 295 | 296 | ```javascript 297 | import prisma from "../prisma/client.js"; 298 | 299 | class InstitutionRepository { 300 | async create(data) { 301 | return await prisma.institution.create({ data }); 302 | } 303 | 304 | async findAll() { 305 | return await prisma.institution.findMany(); 306 | } 307 | 308 | async findById(id) { 309 | return await prisma.institution.findUnique({ 310 | where: { id }, 311 | }); 312 | } 313 | 314 | async update(id, data) { 315 | return await prisma.institution.update({ 316 | where: { id }, 317 | data, 318 | }); 319 | } 320 | 321 | async delete(id) { 322 | return await prisma.institution.delete({ 323 | where: { id }, 324 | }); 325 | } 326 | } 327 | 328 | export default new InstitutionRepository(); 329 | ``` 330 | 331 | In the `controllers/v1/institution.js` file, update the following code. 332 | 333 | ```javascript 334 | import institutionRepository from "../../repositories/institution.js"; 335 | 336 | const createInstitution = async (req, res) => { 337 | try { 338 | await institutionRepository.create(req.body); 339 | const newInstitutions = await institutionRepository.findAll(); 340 | return res.status(201).json({ 341 | message: "Institution successfully created", 342 | data: newInstitutions, 343 | }); 344 | } catch (err) { 345 | return res.status(500).json({ 346 | message: err.message, 347 | }); 348 | } 349 | }; 350 | 351 | const getInstitutions = async (req, res) => { 352 | try { 353 | const institutions = await institutionRepository.findAll(); 354 | if (!institutions) { 355 | return res.status(404).json({ message: "No institutions found" }); 356 | } 357 | return res.status(200).json({ 358 | data: institutions, 359 | }); 360 | } catch (err) { 361 | return res.status(500).json({ 362 | message: err.message, 363 | }); 364 | } 365 | }; 366 | 367 | const getInstitution = async (req, res) => { 368 | try { 369 | const institution = await institutionRepository.findById(req.params.id); 370 | if (!institution) { 371 | return res.status(404).json({ 372 | message: `No institution with the id: ${req.params.id} found`, 373 | }); 374 | } 375 | return res.status(200).json({ 376 | data: institution, 377 | }); 378 | } catch (err) { 379 | return res.status(500).json({ 380 | message: err.message, 381 | }); 382 | } 383 | }; 384 | 385 | const updateInstitution = async (req, res) => { 386 | try { 387 | let institution = await institutionRepository.findById(req.params.id); 388 | if (!institution) { 389 | return res.status(404).json({ 390 | message: `No institution with the id: ${req.params.id} found`, 391 | }); 392 | } 393 | institution = await institutionRepository.update(req.params.id, req.body); 394 | return res.status(200).json({ 395 | message: `Institution with the id: ${req.params.id} successfully updated`, 396 | data: institution, 397 | }); 398 | } catch (err) { 399 | return res.status(500).json({ 400 | message: err.message, 401 | }); 402 | } 403 | }; 404 | 405 | const deleteInstitution = async (req, res) => { 406 | try { 407 | const institution = await institutionRepository.findById(req.params.id); 408 | if (!institution) { 409 | return res.status(404).json({ 410 | message: `No institution with the id: ${req.params.id} found`, 411 | }); 412 | } 413 | await institutionRepository.delete(req.params.id); 414 | return res.json({ 415 | message: `Institution with the id: ${req.params.id} successfully deleted`, 416 | }); 417 | } catch (err) { 418 | return res.status(500).json({ 419 | message: err.message, 420 | }); 421 | } 422 | }; 423 | 424 | export { 425 | createInstitution, 426 | getInstitutions, 427 | getInstitution, 428 | updateInstitution, 429 | deleteInstitution, 430 | }; 431 | ``` 432 | 433 | --- 434 | 435 | ## Formative Assessment 436 | 437 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 438 | 439 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 440 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 441 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 442 | --- 443 | 444 | ### Task One 445 | 446 | Implement the code examples above. 447 | 448 | --- 449 | 450 | ### Task Two 451 | 452 | In the `controllers` and `routes` directories, there is a lot of duplicate code. Refactor the code to reduce the duplication. 453 | 454 | --- 455 | 456 | ### Task Three 457 | 458 | Create a `User` model with the following fields: 459 | 460 | - `id` 461 | - `firstName` 462 | - `lastName` 463 | - `emailAddress` which should be unique 464 | - `password` which does not need to be hashed 465 | - `createdAt` 466 | - `updatedAt` 467 | 468 | Create the necessary controller, router and repository files for the `User` model. 469 | 470 | In the router file, create Swagger documentation for the following routes: 471 | 472 | - GET `/api/v1/users` 473 | - GET `/api/v1/users/{id}` 474 | - POST `/api/v1/users` 475 | - PUT `/api/v1/users/{id}` 476 | - DELETE `/api/v1/users/{id}` 477 | 478 | --- 479 | 480 | ### Task Four 481 | 482 | Create a `Course` model with the following fields: 483 | 484 | - `id` 485 | - `code` 486 | - `name` 487 | - `description` 488 | - `departmentId` 489 | - `userId` 490 | - `createdAt` 491 | - `updatedAt` 492 | 493 | Create the necessary controller, router and repository files for the `Course` model. 494 | 495 | In the router file, create **Swagger** documentation for the following routes: 496 | 497 | - GET `/api/v1/courses` 498 | - GET `/api/v1/courses/{id}` 499 | - POST `/api/v1/courses` 500 | - PUT `/api/v1/courses/{id}` 501 | - DELETE `/api/v1/courses/{id}` 502 | 503 | --- 504 | 505 | ### Task Five (Independent Research) 506 | 507 | You notice there is a lot of code duplication. Refactor the code to reduce the duplication. 508 | 509 | --- 510 | 511 | ## Next Class 512 | 513 | Link to the next class: [Week 05](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-05-validation-filtering-sorting-api-testing.md) 514 | -------------------------------------------------------------------------------- /lecture-notes/week-05-validation-filtering-sorting.md: -------------------------------------------------------------------------------- 1 | # Week 05 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 04](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-04-api-versioning-relationships-repository-pattern.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-05-formative-assessment** from **week-04-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## Validation 18 | 19 | Validation is the process of ensuring that data is correct and meets certain criteria before it is used or stored. In the context of web development, validation is often used to ensure that user input is correct and meets the requirements of the application. 20 | 21 | --- 22 | 23 | ## Setup 24 | 25 | To get started, open a terminal and run the following. 26 | 27 | ```bash 28 | npm install joi 29 | ``` 30 | 31 | > **Note:** There are several ways to validate data in a Node.js application. You could write your own validation logic, use a library like Joi, or use a validation framework like Express Validator. 32 | 33 | --- 34 | 35 | ## Validation Middleware 36 | 37 | In the `middleware` directory, create a new directory called `validation`. In the `validation` directory, create a new file called `institution.js`. In the `institution.js` file, add the following code. 38 | 39 | ```javascript 40 | import Joi from "joi"; 41 | 42 | const validatePostInstitution = (req, res, next) => { 43 | const institutionSchema = Joi.object({ 44 | name: Joi.string().min(3).max(100).required().messages({ 45 | "string.base": "name should be a string", 46 | "string.empty": "name cannot be empty", 47 | "string.min": "name should have a minimum length of {#limit}", 48 | "string.max": "name should have a maximum length of {#limit}", 49 | "any.required": "name is required", 50 | }), 51 | region: Joi.string().min(3).max(100).required().messages({ 52 | "string.base": "region should be a string", 53 | "string.empty": "region cannot be empty", 54 | "string.min": "region should have a minimum length of {#limit}", 55 | "string.max": "region should have a maximum length of {#limit}", 56 | "any.required": "region is required", 57 | }), 58 | country: Joi.string().min(3).max(100).required().messages({ 59 | "string.base": "country should be a string", 60 | "string.empty": "country cannot be empty", 61 | "string.min": "country should have a minimum length of {#limit}", 62 | "string.max": "country should have a maximum length of {#limit}", 63 | "any.required": "country is required", 64 | }), 65 | }); 66 | 67 | const { error } = institutionSchema.validate(req.body); 68 | 69 | if (error) { 70 | return res.status(409).json({ 71 | message: error.details[0].message, 72 | }); 73 | } 74 | 75 | next(); 76 | }; 77 | 78 | const validatePutInstitution = (req, res, next) => { 79 | const institutionSchema = Joi.object({ 80 | name: Joi.string().min(3).max(100).optional().messages({ 81 | "string.base": "name should be a string", 82 | "string.empty": "name cannot be empty", 83 | "string.min": "name should have a minimum length of {#limit}", 84 | "string.max": "name should have a maximum length of {#limit}", 85 | }), 86 | region: Joi.string().min(3).max(100).optional().messages({ 87 | "string.base": "region should be a string", 88 | "string.empty": "region cannot be empty", 89 | "string.min": "region should have a minimum length of {#limit}", 90 | "string.max": "region should have a maximum length of {#limit}", 91 | }), 92 | country: Joi.string().min(3).max(100).optional().messages({ 93 | "string.base": "country should be a string", 94 | "string.empty": "country cannot be empty", 95 | "string.min": "country should have a minimum length of {#limit}", 96 | "string.max": "country should have a maximum length of {#limit}", 97 | }), 98 | }).min(1); // Ensure at least one field is being updated 99 | 100 | const { error } = institutionSchema.validate(req.body); 101 | 102 | if (error) { 103 | return res.status(409).json({ 104 | message: error.details[0].message, 105 | }); 106 | } 107 | 108 | next(); 109 | }; 110 | 111 | export { validatePostInstitution, validatePutInstitution }; 112 | ``` 113 | 114 | --- 115 | 116 | ## Institution Router 117 | 118 | In the `routes/v1` directory, open the `institution.js` file. Update the file as follows. 119 | 120 | ```javascript 121 | import express from "express"; 122 | 123 | import { 124 | createInstitution, 125 | getInstitutions, 126 | getInstitution, 127 | updateInstitution, 128 | deleteInstitution, 129 | } from "../../controllers/v1/institution.js"; 130 | 131 | import { 132 | validatePostInstitution, 133 | validatePutInstitution, 134 | } from "../../middleware/validation/institution.js"; 135 | 136 | const router = express.Router(); 137 | 138 | // Note: Swagger documentation has been removed for brevity 139 | 140 | router.post("/", validatePostInstitution, createInstitution); 141 | router.get("/", getInstitutions); 142 | router.get("/:id", getInstitution); 143 | router.put("/:id", validatePutInstitution, updateInstitution); 144 | router.delete("/:id", deleteInstitution); 145 | 146 | export default router; 147 | ``` 148 | 149 | --- 150 | 151 | ### POST Request Example 152 | 153 | Validating `string.empty`. 154 | 155 | ![](<../resources (ignore)/img/05/swagger-1.PNG>) 156 | 157 | ![](<../resources (ignore)/img/05/swagger-2.PNG>) 158 | 159 | Validating `any.required`. 160 | 161 | ![](<../resources (ignore)/img/05/swagger-3.PNG>) 162 | 163 | ![](<../resources (ignore)/img/05/swagger-4.PNG>) 164 | 165 | --- 166 | 167 | ## Filtering 168 | 169 | Filtering is the process of selecting a subset of resources from a larger collection based on certain criteria. By applying filters to an API request, users can control which resources are returned based on specific conditions. 170 | 171 | --- 172 | 173 | ## Institution Repository 174 | 175 | In the `repositories` directory, open the `institution.js` file. Update the `findAll()` function as follows. 176 | 177 | ```javascript 178 | async findAll(filters = {}) { 179 | // Create an empty query object 180 | const query = {}; 181 | 182 | if (Object.keys(filters).length > 0) { 183 | query.where = {}; 184 | // Loop through the filters and apply them dynamically 185 | for (const [key, value] of Object.entries(filters)) { 186 | if (value) { 187 | query.where[key] = { contains: value }; 188 | } 189 | } 190 | } 191 | 192 | return await prisma.institution.findMany(query); 193 | } 194 | ``` 195 | 196 | --- 197 | 198 | ## Institution Controller 199 | 200 | In the `controllers/v1` directory, open the `institution.js` file. Update the `getInstitutions()` function as follows. 201 | 202 | ```javascript 203 | const getInstitutions = async (req, res) => { 204 | try { 205 | // Extract filters from the query parameters 206 | const filters = { 207 | name: req.query.name || undefined, 208 | region: req.query.region || undefined, 209 | country: req.query.country || undefined, 210 | }; 211 | 212 | // Retrieve institutions based on the filters 213 | const institutions = await institutionRepository.findAll(filters); 214 | 215 | // Check if there are no institutions 216 | if (!institutions) { 217 | return res.status(404).json({ message: "No institutions found" }); 218 | } 219 | 220 | return res.status(200).json({ 221 | data: institutions, 222 | }); 223 | } catch (err) { 224 | return res.status(500).json({ 225 | message: err.message, 226 | }); 227 | } 228 | }; 229 | ``` 230 | 231 | --- 232 | 233 | ## Institution Router 234 | 235 | In the `routes/v1` directory, open the `institution.js` file. In the `/api/v1/institutions:` block under the `tags:` block, add the following code. 236 | 237 | ```javascript 238 | * parameters: 239 | * - in: query 240 | * name: name 241 | * schema: 242 | * type: string 243 | * description: Filter institutions by name 244 | * - in: query 245 | * name: region 246 | * schema: 247 | * type: string 248 | * description: Filter institutions by region 249 | * - in: query 250 | * name: country 251 | * schema: 252 | * type: string 253 | * description: Filter institutions by country 254 | ``` 255 | 256 | --- 257 | 258 | ## GET Request Example 259 | 260 | Here is an example `GET` request that returns all institutions that have the `name` **Otago Polytechnic**: `http://localhost:3000/api/v1/institutions?name=Otago Polytechnic` 261 | 262 | ![](<../resources (ignore)/img/05/swagger-5.PNG>) 263 | 264 | > **Note:** The `%20` in the URL represents a space character. When specifying query parameters in a URL, spaces are often replaced with `%20` to ensure that the URL is properly encoded. 265 | 266 | ## Sorting 267 | 268 | Sorting is the process of arranging a collection of resources in a specific order based on one or more criteria. By applying sorting to the results of an API request, users can control the order in which the resources are returned. 269 | 270 | --- 271 | 272 | ## Institution Repository 273 | 274 | In the `repositories` directory, open the `institution.js` file. Update the `findAll()` function as follows. 275 | 276 | ```javascript 277 | // Find all institutions based on the provided filters, sorted by the specified column and order 278 | async findAll(filters = {}, sortBy = "id", sortOrder = "asc") { 279 | const query = { 280 | orderBy: { 281 | [sortBy]: sortOrder, // Sort by the specified column and order 282 | }, 283 | }; 284 | 285 | if (Object.keys(filters).length > 0) { 286 | query.where = {}; 287 | // Loop through the filters and apply them dynamically 288 | for (const [key, value] of Object.entries(filters)) { 289 | if (value) { 290 | query.where[key] = { contains: value }; 291 | } 292 | } 293 | } 294 | 295 | return await prisma.institution.findMany(query); 296 | } 297 | ``` 298 | 299 | --- 300 | 301 | ## Institution Controller 302 | 303 | In the `controllers/v1` directory, open the `institution.js` file. Update the `getInstitutions()` function as follows. 304 | 305 | ```javascript 306 | const getInstitutions = async (req, res) => { 307 | try { 308 | const filters = { 309 | name: req.query.name || undefined, 310 | region: req.query.region || undefined, 311 | country: req.query.country || undefined, 312 | }; 313 | 314 | // Extract the sortBy and sortOrder parameters from the query 315 | const sortBy = req.query.sortBy || "id"; 316 | const sortOrder = req.query.sortOrder === "desc" ? "desc" : "asc"; 317 | 318 | // Retrieve institutions based on the filters, sorted by the specified column and order 319 | const institutions = await institutionRepository.findAll( 320 | filters, 321 | sortBy, 322 | sortOrder 323 | ); 324 | 325 | // Check if there are no institutions 326 | if (!institutions) { 327 | return res.status(404).json({ message: "No institutions found" }); 328 | } 329 | 330 | return res.status(200).json({ 331 | data: institutions, 332 | }); 333 | } catch (err) { 334 | return res.status(500).json({ 335 | message: err.message, 336 | }); 337 | } 338 | }; 339 | ``` 340 | 341 | > **Note:** Where other functions are using `institutionRepository.findAll()`, pass in the following arguments `({}, "id", "asc")`. For example, `institutionRepository.findAll({}, "id", "asc")`. 342 | 343 | --- 344 | 345 | ## Institution Router 346 | 347 | In the `routes/v1` directory, open the `institution.js` file. In the `/api/v1/institutions:` block under the `tags:` block, add the following code. 348 | 349 | ```javascript 350 | * - in: query 351 | * name: sortBy 352 | * schema: 353 | * type: string 354 | * enum: [id, name, region, country] 355 | * description: Field to sort the institutions by (default is 'id') 356 | * - in: query 357 | * name: sortOrder 358 | * schema: 359 | * type: string 360 | * enum: [asc, desc] 361 | * description: Order to sort the institutions by (default is 'asc') 362 | ``` 363 | 364 | --- 365 | 366 | ## GET Request Example 367 | 368 | Here is an example `GET` request that sorts all institutions by name in ascending order: `http://localhost:3000/api/v1/institutions?sortBy=name&sortOrder=asc` 369 | 370 | ![](<../resources (ignore)/img/05/swagger-6.PNG>) 371 | 372 | ![](<../resources (ignore)/img/05/swagger-7.PNG>) 373 | 374 | --- 375 | 376 | 377 | ## Formative Assessment 378 | 379 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 380 | 381 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 382 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 383 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 384 | --- 385 | 386 | ### Task One 387 | 388 | Implement the code examples above. 389 | 390 | --- 391 | 392 | ### Task Two 393 | 394 | Implement validation for the `Department`, `Course`, and `User` resources. 395 | 396 | --- 397 | 398 | ### Task Three (Independent Research) 399 | 400 | Implement a **GET** route that returns an appropriate message if an endpoint does not exist. 401 | 402 | --- 403 | 404 | ### Task Four (Independent Research) 405 | 406 | Pagination is the process of dividing a large collection of resources into smaller pages to improve performance and user experience. By paginating the results of an API request, users can retrieve a subset of resources at a time, rather than loading the entire collection at once. 407 | 408 | Use the this resource - [Prisma Pagination](https://www.prisma.io/docs/orm/prisma-client/queries/pagination) to implement pagination: 409 | 410 | --- 411 | 412 | ### Task Five (Independent Research) 413 | 414 | You notice there is a lot of code duplication. Refactor the code to reduce the duplication. 415 | 416 | --- 417 | 418 | ## Next Class 419 | 420 | Link to the next class: [Week 06](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-06-seeding-api-testing.md) 421 | -------------------------------------------------------------------------------- /lecture-notes/week-06-seeding-api-testing-render-deployment.md: -------------------------------------------------------------------------------- 1 | # Week 06 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 05](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-05-validation-filtering-sorting-api-testing.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-06-formative-assessment** from **week-05-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## Seeding 18 | 19 | **Seeding** is the process of populating a database with data. It is useful for development purposes. There are several ways to seed a database. For this class, we will focus on two methods: 20 | 21 | 1. **Prisma Client**: Use the Prisma Client to seed the database with data. 22 | 2. **GitHub Gist**: Use a GitHub Gist to seed the database with data. 23 | 24 | In the **formative assessment**, you will research and implement a third and fourth method to seed your database. 25 | 26 | --- 27 | 28 | ### Script to Seed Data 29 | 30 | Before we create our tests, let us create a script to seed our database with data. In the `prisma` directory, create a new directory called `seeding`. In the `seeding` directory, create a new file named `seed-institutions.js` and add the following code. 31 | 32 | ```javascript 33 | import prisma from "../client.js"; 34 | 35 | import { validatePostInstitution } from "../../middleware/validation/institution.js"; 36 | 37 | // Simulate an Express-like request and response for validation 38 | const validateInstitution = (institution) => { 39 | const req = { body: institution }; 40 | const res = { 41 | status: (code) => ({ 42 | json: (message) => { 43 | console.log(message.message); 44 | process.exit(1); 45 | }, 46 | }), 47 | }; 48 | 49 | validatePostInstitution(req, res, () => {}); // Pass an empty function since we're not using next() 50 | }; 51 | 52 | const seedInstitutions = async () => { 53 | try { 54 | // Delete all existing institutions 55 | await prisma.institution.deleteMany(); 56 | 57 | const institutionData = [ 58 | { 59 | name: "Otago Polytechnic", 60 | region: "Otago", 61 | country: "New Zealand", 62 | }, 63 | { 64 | name: "Southern Institute of Technology", 65 | region: "Southland", 66 | country: "New Zealand", 67 | }, 68 | ]; 69 | 70 | const data = await Promise.all( 71 | institutionData.map(async (institution) => { 72 | validateInstitution(institution); 73 | return { ...institution }; 74 | }) 75 | ); 76 | 77 | await prisma.institution.createMany({ 78 | data: data, 79 | skipDuplicates: true, // Prevent duplicate entries if the email already exists 80 | }); 81 | 82 | console.log("Institutions successfully seeded"); 83 | } catch (err) { 84 | console.log("Seeding failed:", err.message); 85 | } 86 | }; 87 | 88 | seedInstitutions(); 89 | ``` 90 | 91 | --- 92 | 93 | ## Seeding Data via GitHub Gist 94 | 95 | **GitHub Gist** is a simple way to share snippets and pastes with others. We can use GitHub Gist to store our seed data and fetch it to seed our database. 96 | 97 | --- 98 | 99 | ### Create a GitHub Gist 100 | 101 | Create a [GitHub Gist](https://gist.github.com/) and add the following JSON data. 102 | 103 | ```json 104 | [ 105 | { 106 | "name": "University of Auckland", 107 | "region": "Auckland", 108 | "country": "New Zealand" 109 | }, 110 | { 111 | "name": "University of Waikato", 112 | "region": "Waikato", 113 | "country": "New Zealand" 114 | } 115 | ] 116 | ``` 117 | 118 | Provide the filename as `seed-institutions-github.json` and click on the **Create secret gist** button. 119 | 120 | --- 121 | 122 | ### Getting the Raw URL 123 | 124 | Click on the **Raw** button to get the raw URL of the **GitHub Gist**. Copy the URL. 125 | 126 | --- 127 | 128 | ### Fetching Data from GitHub Gist 129 | 130 | To fetch data from the **GitHub Gist**, we will use the `node-fetch` package. Install the package by running the following command. 131 | 132 | ```bash 133 | npm install node-fetch 134 | ``` 135 | 136 | --- 137 | 138 | ### Script to Seed Data 139 | 140 | In the `prisma/seeding` directory, create a new file named `seed-institutions-github.js` and add the following code. 141 | 142 | ```javascript 143 | import fetch from "node-fetch"; 144 | 145 | import prisma from "../client.js"; 146 | 147 | import { validatePostInstitution } from "../../middleware/validation/institution.js"; 148 | 149 | // Simulate an Express-like request and response for validation 150 | const validateInstitution = (institution) => { 151 | const req = { body: institution }; 152 | const res = { 153 | status: (code) => ({ 154 | json: (message) => { 155 | console.log(message.message); 156 | process.exit(1); 157 | }, 158 | }), 159 | }; 160 | 161 | validatePostInstitution(req, res, () => {}); // Pass an empty function since we're not using next() 162 | }; 163 | 164 | const seedInstitutionsFromGitHub = async () => { 165 | try { 166 | const gistUrl = ""; // Replace with the raw URL of your GitHub Gist 167 | const response = await fetch(gistUrl); 168 | const institutionData = await response.json(); 169 | 170 | const data = await Promise.all( 171 | institutionData.map(async (institution) => { 172 | validateInstitution(institution); 173 | return { ...institution }; 174 | }) 175 | ); 176 | 177 | await prisma.institution.createMany({ 178 | data: data, 179 | skipDuplicates: true, // Prevent duplicate entries if the email already exists 180 | }); 181 | 182 | console.log("Institutions successfully seeded from GitHub Gist"); 183 | } catch (err) { 184 | console.log("Seeding failed:", err.message); 185 | } 186 | }; 187 | 188 | seedInstitutionsFromGitHub(); 189 | ``` 190 | 191 | > **Note:** Replace `` with the raw URL of your **GitHub Gist**. 192 | 193 | --- 194 | 195 | ## Package JSON File 196 | 197 | In the `package.json` file, add the following in the `scripts` block. 198 | 199 | ```json 200 | "prisma:seed-institutions": "node ./prisma/seeding/seed-institutions.js && node ./prisma/seeding/seed-institutions-github.js" 201 | ``` 202 | 203 | --- 204 | 205 | ## API Testing 206 | 207 | **API testing** is a type of software testing that involves testing APIs directly and as part of integration testing to determine if they meet expectations for functionality, reliability, performance, and security. 208 | 209 | --- 210 | 211 | ### Setup - Dependencies 212 | 213 | There are several libraries for testing APIs. We will use **Chai** and **Mocha**. **Chai** is an assertion library that works well with **Mocha**, a testing framework. **Chai** provides a lot of flexibility in terms of how you write your assertions. 214 | 215 | Install the libraries by running the following command. 216 | 217 | ```bash 218 | npm install chai@4.3.9 chai-http@4.4.0 mocha --save-dev 219 | ``` 220 | 221 | --- 222 | 223 | ### Test File 224 | 225 | In the root directory, create a directory named `tests`. In the `tests` directory, create a file named `01-institution.test.js` and add the following code. 226 | 227 | ```javascript 228 | import * as chaiModule from "chai"; 229 | import chaiHttp from "chai-http"; 230 | import { describe, it } from "mocha"; 231 | 232 | import app from "../app.js"; 233 | 234 | const chai = chaiModule.use(chaiHttp); 235 | 236 | let institutionId; 237 | 238 | describe("Institutions", () => { 239 | it("should reject non-string name", async () => { 240 | const res = await chai 241 | .request(app) 242 | .post("/api/v1/institutions") 243 | .send({ name: 123, region: "Otago", country: "New Zealand" }); 244 | 245 | chai.expect(res.body.message).to.be.equal("name should be a string"); 246 | }); 247 | 248 | it("should create a valid institution", async () => { 249 | const res = await chai.request(app).post("/api/v1/institutions").send({ 250 | name: "University of Otago", 251 | region: "Otago", 252 | country: "New Zealand", 253 | }); 254 | 255 | chai 256 | .expect(res.body.message) 257 | .to.be.equal("Institution successfully created"); 258 | institutionId = res.body.data[0].id; 259 | }); 260 | 261 | it("should retrieve all institutions", async () => { 262 | const res = await chai.request(app).get("/api/v1/institutions"); 263 | 264 | chai.expect(res.body.data).to.be.an("array"); 265 | }); 266 | 267 | it("should retrieve an institution by ID", async () => { 268 | const res = await chai 269 | .request(app) 270 | .get(`/api/v1/institutions/${institutionId}`); 271 | 272 | chai.expect(res.body.data.name).to.be.equal("University of Otago"); 273 | }); 274 | 275 | it("should filter institutions by name", async () => { 276 | const res = await chai.request(app).get("/api/v1/institutions?name=Otago"); 277 | 278 | chai.expect(res.body.data[0].name).to.be.equal("University of Otago"); 279 | }); 280 | 281 | it("should reject non-string country during update", async () => { 282 | const res = await chai 283 | .request(app) 284 | .put(`/api/v1/institutions/${institutionId}`) 285 | .send({ 286 | name: "University of Auckland", 287 | region: "Auckland", 288 | country: 123, 289 | }); 290 | 291 | chai.expect(res.body.message).to.be.equal("country should be a string"); 292 | }); 293 | 294 | it("should update a valid institution", async () => { 295 | const res = await chai 296 | .request(app) 297 | .put(`/api/v1/institutions/${institutionId}`) 298 | .send({ 299 | name: "University of Auckland", 300 | region: "Auckland", 301 | country: "New Zealand", 302 | }); 303 | 304 | chai 305 | .expect(res.body.message) 306 | .to.be.equal( 307 | `Institution with the id: ${institutionId} successfully updated` 308 | ); 309 | }); 310 | 311 | it("should delete an institution by ID", async () => { 312 | const res = await chai 313 | .request(app) 314 | .delete(`/api/v1/institutions/${institutionId}`); 315 | 316 | chai 317 | .expect(res.body.message) 318 | .to.be.equal( 319 | `Institution with the id: ${institutionId} successfully deleted` 320 | ); 321 | }); 322 | }); 323 | ``` 324 | 325 | > **Note:** This test suite covers the main HTTP methods (GET, POST, PUT, DELETE) for an institution as well as validation, filtering, and sorting. 326 | 327 | What are some key points to note in the test file? 328 | 329 | - `describe`: A function that groups tests together 330 | - `it`: A function that defines a test case 331 | - `chai.request`: A function that sends a request to the API 332 | - `chai.expect`: A function that makes assertions 333 | 334 | --- 335 | 336 | ### Package JSON File 337 | 338 | In the `package.json` file, add the following line under the `scripts` block. 339 | 340 | ```json 341 | "test": "npm run prisma:reset && mocha tests --recursive --timeout 10000 --exit", 342 | ``` 343 | 344 | The `--timeout 10000` flag sets the timeout for each test to 10 seconds. The `--exit` flag exits the process once the tests are complete. The `--recursive` flag allows Mocha to run tests in subdirectories. 345 | 346 | To run the tests, run the following command. 347 | 348 | ```bash 349 | npm run test 350 | ``` 351 | 352 | When you run the tests, you should see the following output. 353 | 354 | ```bash 355 | Institutions 356 | ✓ should reject non-string name 357 | ✓ should create a valid institution 358 | ✓ should retrieve all institutions 359 | ✓ should retrieve an institution by ID 360 | ✓ should filter institutions by name 361 | ✓ should reject non-string country during update 362 | ✓ should update a valid institution 363 | ✓ should delete an institution by ID 364 | 365 | 8 passing 366 | ``` 367 | 368 | --- 369 | 370 | ## Render 371 | 372 | [Render](https://render.com/) is a **cloud platform** that makes it easy for developers and teams to deploy and host **web applications** and **static websites**. 373 | 374 | --- 375 | 376 | ### PostgreSQL Setup 377 | 378 | 1. Click the **New +** button, then click the **Postgres** link. 379 | 380 | 2. Name your **New PostgreSQL**. For example, **id607001-db-prod**. 381 | 382 | 3. Leave the **Instance Type** as **Free**. Click on the **Create Database** button. 383 | 384 | 4. Click on the **Connect** button and the **External** tab. Copy the **External Database URL**. 385 | 386 | --- 387 | 388 | ### Web Service Setup 389 | 390 | 1. Sign up for a **Render** account at [https://dashboard.render.com/](https://dashboard.render.com/). Use your **GitHub** account to sign up. 391 | 392 | 2. Click the **New +** button, then click the **Web Service** link. 393 | 394 | 3. Click the **Git Provider** option. Connect to your **s1-25-intro-app-dev-repo-GitHub username** repository. You may need to authorise **Render** access to your **GitHub** repositories. 395 | 396 | 4. Name your **web service**. For example, **id607001-rest-api**. Change the **Language** to **Node** and **Branch** to **week-06-formative-assessment**. 397 | 398 | > **Note:** As you progress through the next few weeks, you will manually change the **Branch**. 399 | 400 | 5. Change the **Build Command** to `npm install` and **Start Command** to `node app.js`. Leave the **Instance Type** as **Free**. 401 | 402 | 6. Add the environment variable called `DATABASE_URL`. The value should be the **External Database URL** you copied above. 403 | 404 | 7. Click on the **Deploy Web Service** button. 405 | 406 | 8. Keep an eye on the logs. Your **web service** is ready when you see the following message. 407 | 408 | ```bash 409 | Server is listening on port 10000. Visit http://localhost:10000 410 | Your service is live 🎉 411 | ``` 412 | 413 | 9. Scroll to the top of the page and click on your **web service's** URL. 414 | 415 | > **Resource:** 416 | 417 | --- 418 | 419 | ## Formative Assessment 420 | 421 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 422 | 423 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 424 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 425 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 426 | 427 | --- 428 | 429 | ### Task One 430 | 431 | Implement the code examples above. 432 | 433 | --- 434 | 435 | ### Task Two (Independent Research) 436 | 437 | You saw two ways to seed your database. Research and compare the two methods. Research and implement a third method to seed . Here are some ideas to get you started: 438 | 439 | - Use a **JSON** file to seed your database 440 | - Use a **CSV** file to seed your database 441 | - Use a third-party library to seed your database. For example, [Faker.js](https://fakerjs.dev/guide/) to generate fake data 442 | 443 | --- 444 | 445 | ### Task Three (Independent Research) 446 | 447 | Create two new test files in the `tests` directory named `02-department.test.js` and `03-course.test.js`. Implement 10 tests for each. Make sure you cover the main HTTP methods (GET, POST, PUT, DELETE) as well as validation, filtering, and sorting. 448 | 449 | --- 450 | 451 | ### Task Four (Independent Research) 452 | 453 | In this task, you will research different testing techniques. In your own words, explain the following testing techniques: 454 | 455 | - **Unit Testing** 456 | - **Integration Testing** 457 | - **End-to-End Testing** 458 | 459 | Write your answers in a file called `week-06.md`. Appropriately cite your sources using **APA 7th Edition**. 460 | 461 | --- 462 | 463 | ### Task Five (Independent Research) 464 | 465 | You notice there is a lot of code duplication. Refactor the code to reduce the duplication. 466 | 467 | --- 468 | 469 | ## Next Class 470 | 471 | Link to the next class: [Week 07](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-07-logging-authentication-jail.md 472 | -------------------------------------------------------------------------------- /lecture-notes/week-07-logging-authentication-jail.md: -------------------------------------------------------------------------------- 1 | # Week 07 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 06](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-06-seeding-api-testing.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-07-formative-assessment** from **week-06-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## Logging 18 | 19 | **Logging** is the process of recording events that occur in an application. It is a crucial part of the development process. Logging helps developers understand what is happening in the application, especially when something goes wrong. It is also useful for monitoring the application's performance and security. There are different types of logs, such as error logs, warning logs, info logs, and debug logs. Each type of log provides different information about the application's behaviour. 20 | 21 | --- 22 | 23 | ### Setup 24 | 25 | To get started, run the following command: 26 | 27 | ```bash 28 | npm install winston 29 | ``` 30 | 31 | Check the `package.json` file to ensure you have installed `winston`. 32 | 33 | --- 34 | 35 | ### Middleware 36 | 37 | In the `middleware` directory, create a new file called `logger.js`. In the `logger.js` file, add the following code: 38 | 39 | ```js 40 | import winston from "winston"; 41 | 42 | const { combine, timestamp, printf, colorize, errors, json } = winston.format; 43 | 44 | // Define custom log format 45 | const logFormat = printf(({ level, message, timestamp, stack }) => { 46 | return `${timestamp} [${level}]: ${stack || message}`; 47 | }); 48 | 49 | const isProduction = process.env.APP_ENV === "production"; 50 | 51 | const logger = winston.createLogger({ 52 | level: process.env.LOG_LEVEL || "info", 53 | format: combine( 54 | timestamp({ format: "YYYY-MM-DD HH:mm:ss" }), 55 | errors({ stack: true }), 56 | isProduction ? json() : combine(colorize(), logFormat) 57 | ), 58 | defaultMeta: { service: "user-service" }, 59 | transports: [ 60 | new winston.transports.File({ filename: "error.log", level: "error" }), 61 | new winston.transports.File({ filename: "combined.log" }), 62 | ], 63 | }); 64 | 65 | // Add console transport only in non-production environments 66 | if (!isProduction) { 67 | logger.add( 68 | new winston.transports.Console({ 69 | format: combine(colorize(), logFormat), 70 | }) 71 | ); 72 | } 73 | 74 | export default logger; 75 | ``` 76 | 77 | What is this code doing? 78 | 79 | - The `combine` function combines multiple formats into one format 80 | - The `timestamp` function adds a timestamp to the log message 81 | - The `printf` function formats the log message 82 | - The `colorize` function adds colour to the log message 83 | - The `errors` function logs the error stack trace 84 | - The `createLogger` function creates a logger instance 85 | - The `transports` property specifies where the logs should be stored 86 | - The `add` function adds a transport to the logger instance 87 | 88 | --- 89 | 90 | ### Main File 91 | 92 | In the `app.js` file, add the following import: 93 | 94 | ```js 95 | import logger from "./middleware/logger.js"; 96 | ``` 97 | 98 | Add the following code to the `app.js` file: 99 | 100 | ```js 101 | app.use((req, res, next) => { 102 | logger.info(`${req.method} ${req.originalUrl}`); 103 | next(); 104 | }); 105 | ``` 106 | 107 | > **Note:** Put this code before the routes. 108 | 109 | --- 110 | 111 | ## Authentication and JWT 112 | 113 | As a developer, you ideally want to safeguard sensitive data from being accessed by unauthorised users. Only when a user has logged in or authenticated they will be able to access their data. However, authorisation goes beyond authentication. Users can have different roles and permissions, which gives them specific access. For example, an admin user can create, update and delete a resource, but a normal user can only read a resource. 114 | 115 | --- 116 | 117 | ### Token vs. Session 118 | 119 | Token based authentication is stateless, session based authentication is stateful. This means that the server does not need to keep track of the user's session. The user sends the token with every request, and the server can verify the token without having to store any information about the user. 120 | 121 | --- 122 | 123 | ### JSON Web Tokens (JWT) 124 | 125 | **JWT** is an open standard that defines a compact and self-contained way for securely transmitting information between parties as a JSON object. This information can be verified and trusted because it is digitally signed. **JWTs** can be signed using a secret or a public/private key pair. 126 | 127 | --- 128 | 129 | ### Setup 130 | 131 | To get started, run the following command: 132 | 133 | ```bash 134 | npm install bcryptjs jsonwebtoken 135 | ``` 136 | 137 | > **Note:** The `bcryptjs` package is used to hash passwords, and the `jsonwebtoken` package is used to sign and verify **JWTs**. 138 | 139 | Check the `package.json` file to ensure you have installed `bcryptjs` and `jsonwebtoken`. 140 | 141 | --- 142 | 143 | ### Environment Variables 144 | 145 | In the `.env` file, add the following environment variables: 146 | 147 | ```bash 148 | JWT_SECRET=HelloWorld123 149 | JWT_LIFETIME=1hr 150 | ``` 151 | 152 | The `.env` file should look like this: 153 | 154 | ```bash 155 | APP_ENV=development 156 | DATABASE_URL="postgresql://postgres:HelloWorld123@localhost:5432/postgres" 157 | JWT_SECRET=HelloWorld123 158 | JWT_LIFETIME=1hr 159 | ``` 160 | 161 | You will use the `JWT_SECRET` environment variable's value, i.e., HelloWorld123, to sign the **JWT**. The lifetime of the **JWT** is the `JWT_LIFETIME` environment variable's value, i.e., 1 hour. 162 | 163 | --- 164 | 165 | ### Schema 166 | 167 | In week 04's formative assessment, you were asked to create a `User` model. If you have not done this, in the `prisma.schema` file, add the following model: 168 | 169 | ```js 170 | model User { 171 | id String @id @default(uuid()) 172 | firstName String 173 | lastName String 174 | emailAddress String @unique 175 | password String 176 | loginAttempts Int @default(0) 177 | lastLoginAttempt DateTime? 178 | createdAt DateTime @default(now()) 179 | updatedAt DateTime @default(now()) 180 | } 181 | ``` 182 | 183 | > **Note:** There are two additional fields - `loginAttempts` and `lastLoginAttempt`. 184 | 185 | --- 186 | 187 | ### Middleware 188 | 189 | In the `middleware` directory, create a new file called `jwtAuth.js`. In the `jwtAuth.js` file, add the following code: 190 | 191 | ```js 192 | import jwt from "jsonwebtoken"; 193 | 194 | const jwtAuth = (req, res, next) => { 195 | try { 196 | /** 197 | * The authorization request header provides information that authenticates 198 | * a user agent with a server, allowing access to a protected resource. The 199 | * information will be a bearer token, and a user agent is a middle man between 200 | * you and the server. An example of a user agent is Swagger or a web browser 201 | * like Google Chrome 202 | */ 203 | const authHeader = req.headers.authorization; 204 | 205 | /** 206 | * A bearer token will look something like this - Bearer . A 207 | * response containing a 403 forbidden status code and message 208 | * is returned if a bearer token is not provided 209 | */ 210 | if (!authHeader || !authHeader.startsWith("Bearer ")) { 211 | return res.status(403).json({ 212 | message: "No token provided", 213 | }); 214 | } 215 | 216 | // Get the JWT from the bearer token 217 | const token = authHeader.split(" ")[1]; 218 | 219 | /** 220 | * Verify the signed JWT is valid. The first argument is the token, 221 | * i.e., JWT and the second argument is the secret or public/private key 222 | */ 223 | const payload = jwt.verify(token, process.env.JWT_SECRET); 224 | 225 | // Set Request's user property to the authenticated user 226 | req.user = payload; 227 | 228 | // Call the next middleware in the stack 229 | return next(); 230 | } catch (err) { 231 | return res.status(403).json({ 232 | message: "Not authorized to access this route", 233 | }); 234 | } 235 | }; 236 | 237 | export default jwtAuth; 238 | ``` 239 | 240 | > **Note:** You will use this middleware in the `app.js` file to protect your routes. 241 | 242 | --- 243 | 244 | ### Auth Controller 245 | 246 | In the `controllers/v1` directory, create a new file called `auth.js`. In the `auth.js` file, add the following code: 247 | 248 | ```js 249 | import bcryptjs from "bcryptjs"; 250 | import jwt from "jsonwebtoken"; 251 | 252 | import prisma from "../../prisma/client.js"; 253 | 254 | const register = async (req, res) => { 255 | try { 256 | const { firstName, lastName, emailAddress, password } = req.body; 257 | 258 | let user = await prisma.user.findUnique({ where: { emailAddress } }); 259 | 260 | if (user) return res.status(409).json({ message: "User already exists" }); 261 | 262 | /** 263 | * A salt is random bits added to a password before it is hashed. Salts 264 | * create unique passwords even if two users have the same passwords 265 | */ 266 | const salt = await bcryptjs.genSalt(); 267 | 268 | /** 269 | * Generate a hash for a given string. The first argument 270 | * is a string to be hashed, i.e., Pazzw0rd123 and the second 271 | * argument is a salt, i.e., E1F53135E559C253 272 | */ 273 | const hashedPassword = await bcryptjs.hash(password, salt); 274 | 275 | user = await prisma.user.create({ 276 | data: { firstName, lastName, emailAddress, password: hashedPassword }, 277 | select: { 278 | // Select only the fields you want to return 279 | id: true, 280 | firstName: true, 281 | lastName: true, 282 | emailAddress: true, 283 | createdAt: true, 284 | updatedAt: true, 285 | }, 286 | }); 287 | 288 | return res.status(201).json({ 289 | message: "User successfully registered", 290 | data: user, 291 | }); 292 | } catch (err) { 293 | return res.status(500).json({ 294 | message: err.message, 295 | }); 296 | } 297 | }; 298 | 299 | const login = async (req, res) => { 300 | const MAX_LOGIN_ATTEMPTS = 5; 301 | const LOCK_TIME_MS = 5 * 60 * 1000; // 5 minutes 302 | 303 | try { 304 | const { emailAddress, password } = req.body; 305 | 306 | const user = await prisma.user.findUnique({ where: { emailAddress } }); 307 | 308 | if (!user) 309 | return res.status(401).json({ message: "Invalid email address" }); 310 | 311 | if ( 312 | user.loginAttempts >= MAX_LOGIN_ATTEMPTS && 313 | user.lastLoginAttempt >= Date.now() - LOCK_TIME_MS 314 | ) { 315 | return res.status(401).json({ 316 | message: "Maximum login attempts reached. Please try again later", 317 | }); 318 | } 319 | 320 | /** 321 | * Compare the given string, i.e., Pazzw0rd123, with the given 322 | * hash, i.e., user's hashed password 323 | */ 324 | const isPasswordCorrect = await bcryptjs.compare(password, user.password); 325 | 326 | if (!isPasswordCorrect) { 327 | await prisma.user.update({ 328 | where: { emailAddress }, 329 | data: { 330 | loginAttempts: user.loginAttempts + 1, 331 | lastLoginAttempt: new Date(), 332 | }, 333 | }); 334 | 335 | return res.status(401).json({ message: "Invalid password" }); 336 | } 337 | 338 | const { JWT_SECRET, JWT_LIFETIME } = process.env; 339 | 340 | /** 341 | * Return a JWT. The first argument is the payload, i.e., an object containing 342 | * the authenticated user's id and name, the second argument is the secret 343 | * or public/private key, and the third argument is the lifetime of the JWT 344 | */ 345 | const token = jwt.sign( 346 | { 347 | id: user.id, 348 | name: user.name, 349 | }, 350 | JWT_SECRET, 351 | { expiresIn: JWT_LIFETIME } 352 | ); 353 | 354 | await prisma.user.update({ 355 | where: { emailAddress }, 356 | data: { 357 | loginAttempts: 0, 358 | lastLoginAttempt: null, 359 | }, 360 | }); 361 | 362 | return res.status(200).json({ 363 | message: "User successfully logged in", 364 | token: token, 365 | }); 366 | } catch (err) { 367 | console.error(err.message); 368 | return res.status(500).json({ 369 | message: err.message, 370 | }); 371 | } 372 | }; 373 | 374 | export { register, login }; 375 | ``` 376 | 377 | --- 378 | 379 | ### Auth Router 380 | 381 | In the `routes/v1` directory, create a new file called `auth.js`. In the `auth.js` file, add the following code: 382 | 383 | ```js 384 | import { Router } from "express"; 385 | 386 | import { register, login } from "../../controllers/v1/auth.js"; 387 | 388 | const router = Router(); 389 | 390 | /** 391 | * @swagger 392 | * components: 393 | * schemas: 394 | * User: 395 | * type: object 396 | * properties: 397 | * id: 398 | * type: string 399 | * format: uuid 400 | * example: "123e4567-e89b-12d3-a456-426614174000" 401 | * firstName: 402 | * type: string 403 | * example: "John" 404 | * lastName: 405 | * type: string 406 | * example: "Doe" 407 | * emailAddress: 408 | * type: string 409 | * format: email 410 | * example: "john.doe@example.com" 411 | * password: 412 | * type: string 413 | * example: "password123" 414 | * loginAttempts: 415 | * type: integer 416 | * example: 3 417 | * lastLoginAttempt: 418 | * type: string 419 | * format: date-time 420 | * example: "2024-07-14T12:34:56Z" 421 | * createdAt: 422 | * type: string 423 | * format: date-time 424 | * example: "2024-07-14T12:34:56Z" 425 | * updatedAt: 426 | * type: string 427 | * format: date-time 428 | * example: "2024-07-14T12:34:56Z" 429 | */ 430 | 431 | /** 432 | * @swagger 433 | * /api/v1/auth/register: 434 | * post: 435 | * summary: Register a new user 436 | * tags: 437 | * - Auth 438 | * requestBody: 439 | * required: true 440 | * content: 441 | * application/json: 442 | * schema: 443 | * $ref: '#/components/schemas/User' 444 | * responses: 445 | * '201': 446 | * description: User successfully registered 447 | * content: 448 | * application/json: 449 | * schema: 450 | * type: object 451 | * properties: 452 | * message: 453 | * type: string 454 | * example: "User successfully registered" 455 | * data: 456 | * $ref: '#/components/schemas/User' 457 | * '409': 458 | * description: User already exists 459 | * content: 460 | * application/json: 461 | * schema: 462 | * type: object 463 | * properties: 464 | * message: 465 | * type: string 466 | * example: "User already exists" 467 | * '500': 468 | * description: Internal server error 469 | * content: 470 | * application/json: 471 | * schema: 472 | * type: object 473 | * properties: 474 | * message: 475 | * type: string 476 | * example: "An unexpected error occurred" 477 | */ 478 | router.route("/register").post(register); 479 | 480 | /** 481 | * @swagger 482 | * /api/v1/auth/login: 483 | * post: 484 | * summary: Log in an existing user 485 | * tags: 486 | * - Auth 487 | * requestBody: 488 | * required: true 489 | * content: 490 | * application/json: 491 | * schema: 492 | * type: object 493 | * properties: 494 | * emailAddress: 495 | * type: string 496 | * format: email 497 | * example: "john.doe@example.com" 498 | * password: 499 | * type: string 500 | * example: "password123" 501 | * responses: 502 | * '200': 503 | * description: User successfully logged in 504 | * content: 505 | * application/json: 506 | * schema: 507 | * type: object 508 | * properties: 509 | * message: 510 | * type: string 511 | * example: "User successfully logged in" 512 | * token: 513 | * type: string 514 | * example: "" 515 | * '500': 516 | * description: Internal server error 517 | * content: 518 | * application/json: 519 | * schema: 520 | * type: object 521 | * properties: 522 | * message: 523 | * type: string 524 | * example: "An unexpected error occurred" 525 | */ 526 | router.route("/login").post(login); 527 | 528 | export default router; 529 | ``` 530 | 531 | --- 532 | 533 | ### Institution Router 534 | 535 | In the `routes/v1` directory, open the `institution.js` file. In the `institution.js` file, update the **Swagger documentation**. 536 | 537 | ```js 538 | /** 539 | * @swagger 540 | * components: 541 | * schemas: 542 | * Institution: 543 | * type: object 544 | * properties: 545 | * id: 546 | * type: string 547 | * format: uuid 548 | * example: "123e4567-e89b-12d3-a456-426614174000" 549 | * name: 550 | * type: string 551 | * example: "Institution Name" 552 | * region: 553 | * type: string 554 | * example: "Region Name" 555 | * country: 556 | * type: string 557 | * example: "Country Name" 558 | * createdAt: 559 | * type: string 560 | * format: date-time 561 | * example: "2024-07-14T12:34:56Z" 562 | * updatedAt: 563 | * type: string 564 | * format: date-time 565 | * example: "2024-07-14T12:34:56Z" 566 | * securitySchemes: 567 | * BearerAuth: 568 | * type: http 569 | * scheme: bearer 570 | * bearerFormat: JWT 571 | * security: 572 | * - BearerAuth: [] 573 | */ 574 | ``` 575 | 576 | Add the `security` block under the `tags` block". 577 | 578 | ```js 579 | /** 580 | * @swagger 581 | * /api/v1/institutions: 582 | * post: 583 | * summary: Create a new institution 584 | * tags: 585 | * - Institution 586 | * security: 587 | * - BearerAuth: [] 588 | ``` 589 | 590 | ### Main File 591 | 592 | In the `app.js` file, add the following imports: 593 | 594 | ```js 595 | import jwtAuth from "./middleware/jwtAuth.js"; 596 | 597 | import authRoutes from "./routes/v1/auth.js"; 598 | ``` 599 | 600 | Add the following route for `/auth`: 601 | 602 | ```js 603 | app.use("/api/v1/auth", authRoutes); 604 | ``` 605 | 606 | Update the following routes for `/institutions`: 607 | 608 | ```js 609 | app.use("/api/v1/institutions", jwtAuth, institutionRoutes); // Authenticated route 610 | ``` 611 | 612 | --- 613 | 614 | You should see two more options - `/api/v1/auth/register` and `/api/v1/auth/login` 615 | 616 | ![](<../resources (ignore)/img/06/capture-1.PNG>) 617 | 618 | ## Register Example 619 | 620 | Registering a new user. What happens to the `password` when you click the **Execute** button? 621 | 622 | ![](<../resources (ignore)/img/06/capture-2.PNG>) 623 | 624 | --- 625 | 626 | ## Login Example 627 | 628 | Logging in with John Doe. 629 | 630 | ![](<../resources (ignore)/img/06/capture-3.PNG>) 631 | 632 | Make sure you copy the `token`. You will need these later on. 633 | 634 | ![](<../resources (ignore)/img/06/capture-4.PNG>) 635 | 636 | What happens if you enter the wrong `password` and click the **Execute** button six times? 637 | 638 | --- 639 | 640 | ## POST Example 641 | 642 | You should see a lock next the down chevron. 643 | 644 | ![](<../resources (ignore)/img/06/capture-5.PNG>) 645 | 646 | If you click on the lock, you will be prompt to enter the `token`. Enter the `token`, click the **Authorize** button, then click the **Close** button. 647 | 648 | ![](<../resources (ignore)/img/06/capture-6.PNG>) 649 | 650 | Click on the **Execute** button. What happens if you do not provide the `token`? 651 | 652 | --- 653 | 654 | ## Formative Assessment 655 | 656 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 657 | 658 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 659 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 660 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 661 | --- 662 | 663 | ### Task One 664 | 665 | Implement the code above for the `Institution`, `Department`, `Course` and `User` models. 666 | 667 | --- 668 | 669 | ### Task Two (Independent Research) 670 | 671 | Implement a logout route. The route should invalidate the token. You can do this by storing the token in a blacklist. When a user logs out, add the token to the blacklist. When a user tries to access a protected route with a blacklisted token, return a 403 forbidden status code and message. 672 | 673 | --- 674 | 675 | ## Next Class 676 | 677 | Link to the next class: [Week 08](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-08-role-based-access-control.md) 678 | -------------------------------------------------------------------------------- /lecture-notes/week-08-role-based-access-control.md: -------------------------------------------------------------------------------- 1 | # Week 08 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 07](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-06-logging-authentication-jail.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-08-formative-assessment** from **week-07-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## Authorisation/Access Control 18 | 19 | Authorisation/access control is a process of determining if a user has the right to access a resource. For example, if a user is logged in, they should be able to access their user data. If a user is not logged in, they should not be able to access their user data. 20 | 21 | --- 22 | 23 | ### Schema 24 | 25 | In the `schema.prisma` file, add a new enum called `Role` with the values `ADMIN` and `BASIC`. 26 | 27 | ```prisma 28 | enum Role { 29 | ADMIN 30 | BASIC 31 | } 32 | ``` 33 | 34 | Update the `User` model to include a `Role` field called `role` with the default value of `BASIC`. 35 | 36 | ```prisma 37 | model User { 38 | id String @id @default(uuid()) 39 | firstName String 40 | lastName String 41 | emailAddress String @unique 42 | password String 43 | loginAttempts Int @default(0) 44 | lastLoginAttempt DateTime? 45 | role Role @default(BASIC) 46 | createdAt DateTime @default(now()) 47 | updatedAt DateTime @default(now()) 48 | } 49 | ``` 50 | 51 | --- 52 | 53 | ### Auth Controller 54 | 55 | In the `controllers/v1/auth.js` file, refactor the register function with the following code: 56 | 57 | ```js 58 | const register = async (req, res) => { 59 | try { 60 | const { firstName, lastName, emailAddress, password, role } = req.body; 61 | 62 | if (role === "ADMIN") { 63 | return res 64 | .status(403) 65 | .json({ message: "User cannot register as an admin" }); 66 | } 67 | 68 | let user = await prisma.user.findUnique({ where: { emailAddress } }); 69 | 70 | if (user) return res.status(409).json({ message: "User already exists" }); 71 | 72 | const salt = await bcryptjs.genSalt(); 73 | const hashedPassword = await bcryptjs.hash(password, salt); 74 | 75 | user = await prisma.user.create({ 76 | data: { 77 | firstName, 78 | lastName, 79 | emailAddress, 80 | password: hashedPassword, 81 | role: "BASIC", 82 | }, 83 | select: { 84 | id: true, 85 | firstName: true, 86 | lastName: true, 87 | emailAddress: true, 88 | createdAt: true, 89 | updatedAt: true, 90 | }, 91 | }); 92 | 93 | return res.status(201).json({ 94 | message: "User successfully registered", 95 | data: user, 96 | }); 97 | } catch (err) { 98 | return res.status(500).json({ 99 | message: err.message, 100 | }); 101 | } 102 | }; 103 | ``` 104 | 105 | --- 106 | 107 | ### Authorisation Middleware 108 | 109 | In the `middleware/auth` directory, create a new file called `authorisation.js`. In the `authorisation.js` file, add the following code: 110 | 111 | ```js 112 | import prisma from "../../prisma/client.js"; 113 | 114 | const authorisation = async (req, res, next) => { 115 | try { 116 | const { id } = req.user; 117 | 118 | const user = await prisma.user.findUnique({ where: { id: id } }); 119 | 120 | // Check if the user is an admin 121 | if (user.role !== "ADMIN") { 122 | return res.status(403).json({ 123 | message: "Not authorized to access this route", 124 | }); 125 | } 126 | 127 | next(); 128 | } catch (err) { 129 | return res.status(500).json({ 130 | message: err.message, 131 | }); 132 | } 133 | }; 134 | 135 | export default authorisation; 136 | ``` 137 | 138 | --- 139 | 140 | ### Institution Router 141 | 142 | ```js 143 | import express from "express"; 144 | 145 | // Note: Controller and validation imports have been removed for brevity 146 | 147 | import authorisation from "../../middleware/auth/authorisation.js"; 148 | 149 | const router = express.Router(); 150 | 151 | // Note: Swagger documentation has been removed for brevity 152 | 153 | router.post("/", validatePostInstitution, authorisation, createInstitution); 154 | router.get("/", getInstitutions); 155 | router.get("/:id", getInstitution); 156 | router.put("/:id", validatePutInstitution, updateInstitution); 157 | router.delete("/:id", deleteInstitution); 158 | 159 | export default router; 160 | ``` 161 | 162 | --- 163 | 164 | ## Register Example 165 | 166 | Registering a new admin user. 167 | 168 | ![](<../resources (ignore)/img/07/capture-1.PNG>) 169 | 170 | Response from registering a new admin user. 171 | 172 | ![](<../resources (ignore)/img/07/capture-2.PNG>) 173 | 174 | Registering a new basic user. 175 | 176 | ![](<../resources (ignore)/img/07/capture-3.PNG>) 177 | 178 | --- 179 | 180 | ### POST Example 181 | 182 | Creating a new institution as a basic user. 183 | 184 | ![](<../resources (ignore)/img/07/capture-4.PNG>) 185 | 186 | If you want to test this works, **TEMPORARILY** replace `if (user.role !== "ADMIN")` with `if (user.role !== "BASIC")` in the `middleware/authorisation.js` file. 187 | 188 | --- 189 | 190 | ## API Testing 191 | 192 | In the `tests` directory, create a new file called `00-auth.test.js`. Add the following code to the `00-auth.test.js` file: 193 | 194 | ```js 195 | import bcryptjs from "bcryptjs"; 196 | import * as chaiModule from "chai"; 197 | import chaiHttp from "chai-http"; 198 | import { describe, it, before } from "mocha"; 199 | 200 | import app from "../app.js"; 201 | import prisma from "../prisma/client.js"; 202 | 203 | const chai = chaiModule.use(chaiHttp); 204 | 205 | const hashPassword = async (password) => { 206 | const salt = await bcryptjs.genSalt(); 207 | return bcryptjs.hash(password, salt); 208 | }; 209 | 210 | describe("Auth", () => { 211 | before(async () => { 212 | // Ensure a fresh admin user exists in the database 213 | await prisma.user.create({ 214 | data: { 215 | firstName: "John", 216 | lastName: "Doe", 217 | emailAddress: "john.doe@example.com", 218 | password: await hashPassword("password123"), 219 | role: "ADMIN", 220 | }, 221 | }); 222 | }); 223 | 224 | it("should login an admin user and return a token", async () => { 225 | const res = await chai.request(app).post("/api/v1/auth/login").send({ 226 | emailAddress: "john.doe@example.com", 227 | password: "password123", 228 | }); 229 | 230 | chai.expect(res).to.have.status(200); 231 | chai.expect(res.body.token).to.exist; 232 | }); 233 | }); 234 | ``` 235 | 236 | The following example is a refactored version of the `01-institution.test.js` file. The file has been refactored to include the `login` function. The `login` function logs in an admin user and returns the `token`. The `token` is then used to access the protected routes. 237 | 238 | ```js 239 | import * as chaiModule from "chai"; 240 | import chaiHttp from "chai-http"; 241 | import { describe, it } from "mocha"; 242 | 243 | import app from "../app.js"; 244 | 245 | const chai = chaiModule.use(chaiHttp); 246 | 247 | let token; 248 | let institutionId; 249 | 250 | describe("Institutions", () => { 251 | it("should reject missing token", async () => { 252 | const res = await chai.request(app).get("/api/v1/institutions"); 253 | 254 | chai.expect(res.body.message).to.be.equal("No token provided"); 255 | }); 256 | 257 | it("should login an admin user and return a token", async () => { 258 | const res = await chai.request(app).post("/api/v1/auth/login").send({ 259 | emailAddress: "john.doe@example.com", 260 | password: "password123", 261 | }); 262 | 263 | chai.expect(res.body.token).to.exist; 264 | 265 | token = res.body.token; 266 | }); 267 | 268 | it("should reject non-string name", async () => { 269 | const res = await chai 270 | .request(app) 271 | .post("/api/v1/institutions") 272 | .set("Authorization", `Bearer ${token}`) 273 | .send({ name: 123, region: "Otago", country: "New Zealand" }); 274 | 275 | chai.expect(res.body.message).to.be.equal("name should be a string"); 276 | }); 277 | 278 | it("should create a valid institution", async () => { 279 | const res = await chai 280 | .request(app) 281 | .post("/api/v1/institutions") 282 | .set("Authorization", `Bearer ${token}`) 283 | .send({ 284 | name: "University of Otago", 285 | region: "Otago", 286 | country: "New Zealand", 287 | }); 288 | 289 | chai 290 | .expect(res.body.message) 291 | .to.be.equal("Institution successfully created"); 292 | institutionId = res.body.data[0].id; 293 | }); 294 | 295 | it("should retrieve all institutions", async () => { 296 | const res = await chai 297 | .request(app) 298 | .get("/api/v1/institutions") 299 | .set("Authorization", `Bearer ${token}`); 300 | 301 | chai.expect(res.body.data).to.be.an("array"); 302 | }); 303 | 304 | it("should retrieve an institution by ID", async () => { 305 | const res = await chai 306 | .request(app) 307 | .get(`/api/v1/institutions/${institutionId}`) 308 | .set("Authorization", `Bearer ${token}`); 309 | 310 | chai.expect(res.body.data.name).to.be.equal("University of Otago"); 311 | }); 312 | 313 | it("should filter institutions by name", async () => { 314 | const res = await chai 315 | .request(app) 316 | .get("/api/v1/institutions?name=Otago") 317 | .set("Authorization", `Bearer ${token}`); 318 | 319 | chai.expect(res.body.data[0].name).to.be.equal("University of Otago"); 320 | }); 321 | 322 | it("should sort institutions by name", async () => { 323 | const res = await chai 324 | .request(app) 325 | .get("/api/v1/institutions?sortBy=name") 326 | .set("Authorization", `Bearer ${token}`); 327 | 328 | chai.expect(res.body.data[0].name).to.be.equal("University of Canterbury"); 329 | }); 330 | 331 | it("should reject non-string country during update", async () => { 332 | const res = await chai 333 | .request(app) 334 | .put(`/api/v1/institutions/${institutionId}`) 335 | .set("Authorization", `Bearer ${token}`) 336 | .send({ 337 | name: "University of Auckland", 338 | region: "Auckland", 339 | country: 123, 340 | }); 341 | 342 | chai.expect(res.body.message).to.be.equal("country should be a string"); 343 | }); 344 | 345 | it("should update a valid institution", async () => { 346 | const res = await chai 347 | .request(app) 348 | .put(`/api/v1/institutions/${institutionId}`) 349 | .set("Authorization", `Bearer ${token}`) 350 | .send({ 351 | name: "University of Auckland", 352 | region: "Auckland", 353 | country: "New Zealand", 354 | }); 355 | 356 | chai 357 | .expect(res.body.message) 358 | .to.be.equal( 359 | `Institution with the id: ${institutionId} successfully updated` 360 | ); 361 | }); 362 | 363 | it("should delete an institution by ID", async () => { 364 | const res = await chai 365 | .request(app) 366 | .delete(`/api/v1/institutions/${institutionId}`) 367 | .set("Authorization", `Bearer ${token}`); 368 | 369 | chai 370 | .expect(res.body.message) 371 | .to.be.equal( 372 | `Institution with the id: ${institutionId} successfully deleted` 373 | ); 374 | }); 375 | }); 376 | ``` 377 | 378 | > **Note:** You will notice that each `chai.request` has been refactored to include the `set` method. The `set` method is used to set the `Authorization` header with the `token`. 379 | 380 | --- 381 | 382 | ## Formative Assessment 383 | 384 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 385 | 386 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 387 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 388 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 389 | --- 390 | 391 | ### Task One 392 | 393 | Implement the code examples above. 394 | 395 | --- 396 | 397 | ## Next Class 398 | 399 | Link to the next class: [Week 08](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-08-rate-limiting-securing-http-headers.md) 400 | -------------------------------------------------------------------------------- /lecture-notes/week-09-rate-limiting-securing-http-headers.md: -------------------------------------------------------------------------------- 1 | # Week 09 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 08](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-08-role-based-access-control.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-09-formative-assessment** from **week-08-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## Securing HTTP Headers 18 | 19 | **Helmet** helps secure your **Express** apps by setting various **HTTP headers**. For example, **X-Powered-By** which is a header that is set by default in **Express**. This header can be used by attackers to identify the technology stack of your application. 20 | 21 | --- 22 | 23 | ### Setup 24 | 25 | To get started, run the following command: 26 | 27 | ```bash 28 | npm install helmet 29 | ``` 30 | 31 | Check the `package.json` file to ensure you have installed `helmet`. 32 | 33 | --- 34 | 35 | ### Main File 36 | 37 | In the `app.js` file, import `helmet`. For example: 38 | 39 | ```js 40 | import helmet from "helmet"; 41 | ``` 42 | 43 | Add the following **middleware**: 44 | 45 | ```js 46 | app.use( 47 | helmet({ 48 | xPoweredBy: true, 49 | }) 50 | ); 51 | ``` 52 | 53 | > **Note:** When you perform an **HTTP** request, you should see the **X-Powered-By** header in the response. After adding the **helmet** middleware, the **X-Powered-By** header should be removed. 54 | 55 | Before. 56 | 57 | ![](<../resources (ignore)/img/09/helmet-1.PNG>) 58 | 59 | After. 60 | 61 | ![](<../resources (ignore)/img/09/helmet-2.PNG>) 62 | 63 | --- 64 | 65 | ## Rate Limiting 66 | 67 | **Express Rate Limit** is a **middleware** that limits repeated requests to public APIs and/or endpoints. 68 | 69 | --- 70 | 71 | ### Setup 72 | 73 | To get started, run the following command: 74 | 75 | ```bash 76 | npm install express-rate-limit 77 | ``` 78 | 79 | Check the `package.json` file to ensure you have installed `express-rate-limit`. 80 | 81 | --- 82 | 83 | ### Main File 84 | 85 | In the `app.js` file, import `rateLimit`. For example: 86 | 87 | ```js 88 | import rateLimit from "express-rate-limit"; 89 | ``` 90 | 91 | Add the following **middleware**: 92 | 93 | ```js 94 | // This should be declared under - app.use( helmet({ xPoweredBy: true, })); 95 | app.use( 96 | rateLimit({ 97 | windowMs: 15 * 60 * 1000, // 15 minutes 98 | max: 100, // limit each IP to 100 requests per windowMs 99 | }) 100 | ); 101 | ``` 102 | 103 | This is a basic example of rate limiting. You can customise the rate limiting to suit your application. For example, you can limit requests based on the user's **IP address**. 104 | 105 | > **Note:** You should see additional headers in the response. For example, **X-RateLimit-Limit**, **X-RateLimit-Remaining**, and **X-RateLimit-Reset**. 106 | 107 | After. 108 | 109 | ![](<../resources (ignore)/img/09/rate-limit-1.PNG>) 110 | 111 | --- 112 | 113 | ## Formative Assessment 114 | 115 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 116 | 117 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 118 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 119 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 120 | --- 121 | 122 | ### Task One 123 | 124 | Implement the code examples above. 125 | 126 | --- 127 | 128 | ### Task Two (Independent Research) 129 | 130 | Update the `rateLimit` function to include a custom message when the rate limit is exceeded. For example, Too many requests from this IP, please try again after 15 minutes. 131 | 132 | --- 133 | 134 | ### Task Three (Independent Research) 135 | 136 | In the `tests` directory, create a new file called `100-http.test.js`. In this file, write a test to check if the **X-Powered-By** header is removed after adding the **helmet** middleware. 137 | 138 | --- 139 | 140 | ### Task Four (Independent Research) 141 | 142 | Implement the following: 143 | 144 | - Cross-Origin Resource Sharing (CORS) middleware - 145 | - Compression middleware - 146 | 147 | In your own words, explain how the **CORS** and **Compression** middleware benefit your application. Appropriately cite your sources using **APA 7th Edition**. 148 | 149 | --- 150 | 151 | ## Next Class 152 | 153 | Link to the next class: [Week 10](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-10-erd-documentation.md) 154 | -------------------------------------------------------------------------------- /lecture-notes/week-10-erd-documentation-software-methodologies.md: -------------------------------------------------------------------------------- 1 | # Week 10 2 | 3 | ## Previous Class 4 | 5 | Link to the previous class: [Week 09](https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/blob/s1-25/lecture-notes/week-09-rate-limiting-securing-http-headers.md) 6 | 7 | --- 8 | 9 | ## Before We Start 10 | 11 | Open your **s1-25-intro-app-dev-repo-GitHub username** repository in **Visual Studio Code**. Create a new branch called **week-10-formative-assessment** from **week-09-formative-assessment**. 12 | 13 | > **Note:** There are a lot of code examples. Typing the code examples rather than copying and pasting is strongly recommended. It will help you remember the code better. Also, read the comments in the code examples. It will help you understand where to type the code. 14 | 15 | --- 16 | 17 | ## ERD Generation 18 | 19 | Instead of using online tools like **Draw.io** and **Lucidchart** to create **ERDs**, you can a library called **prisma-erd-generator** to generate **ERDs**. 20 | 21 | --- 22 | 23 | ### Getting Started 24 | 25 | To install **prisma-erd-generator**, run the following command in your terminal. 26 | 27 | ```bash 28 | npm install prisma-erd-generator --save-dev 29 | ``` 30 | 31 | --- 32 | 33 | ### Schema File 34 | 35 | In the `schema.prisma` file, add the following code. 36 | 37 | ```javascript 38 | generator erd { 39 | provider = "prisma-erd-generator" 40 | } 41 | ``` 42 | 43 | Run the following command to generate the **ERD**: 44 | 45 | ```bash 46 | npx prisma generate 47 | ``` 48 | 49 | You should see an `.svg` file in the `prisma` directory. Open the file to view the **ERD**. 50 | 51 | You can customise the **ERD** by adding the following code to the `schema.prisma` file. 52 | 53 | ```javascript 54 | generator erd { 55 | provider = "prisma-erd-generator" 56 | output = "./prisma/erd.svg" 57 | } 58 | ``` 59 | 60 | > **Resource:** 61 | 62 | --- 63 | 64 | ## Software Methodologies 65 | 66 | Thus far, you should have covered the **Agile** methodology. This week, you will learn about four more methodologies. 67 | 68 | --- 69 | 70 | ### Waterfall 71 | 72 | The **Waterfall** methodology is a linear approach to software development. It is a sequential design process in which progress is seen as flowing steadily downwards through the phases of requirement gathering and analysis, design, implementation, testing, deployment and maintenance. 73 | 74 | > **Resource:** 75 | 76 | --- 77 | 78 | ### Spiral 79 | 80 | The **Spiral** methodology is a risk-driven software development process model. Based on the unique risk patterns of a given project, the spiral model guides a team to adopt elements of one or more process models, such as incremental, waterfall, or evolutionary prototyping. 81 | 82 | > **Resource:** 83 | 84 | --- 85 | 86 | ### V-Model 87 | 88 | The **V-Model** is a type of software development model that takes the form of a V. It is also known as the **Verification and Validation Model**. The V-Model demonstrates the relationships between each phase of the development life cycle and its associated phase of testing. 89 | 90 | > **Resource:** 91 | 92 | --- 93 | 94 | ### RAD 95 | 96 | **Rapid Application Development (RAD)** is a type of incremental software development process model that emphasises an extremely short development cycle. The RAD model is a "high-speed" adaptation of the linear sequential model in which rapid development is achieved by using a component-based construction approach. 97 | 98 | > **Resource:** 99 | 100 | --- 101 | 102 | ## Formative Assessment 103 | 104 | Learning to use AI tools is an important skill. While AI tools are powerful, you **must** be aware of the following: 105 | 106 | - If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 107 | - Do not trust the AI tool's responses blindly. You **must** still use your judgement and may need to do additional research to determine if the response is correct 108 | - Acknowledge what AI tool you have used. In the assessment's repository **README.md** file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 109 | 110 | --- 111 | 112 | ### Task One 113 | 114 | Implement the code examples above. 115 | 116 | --- 117 | 118 | ### Task Two (Independent Research) 119 | 120 | In your own words, explain each phase of the **Waterfall** methodology. Write your answers in a file called `week-10.md`. Appropriately cite your sources using **APA 7th Edition**. 121 | 122 | --- 123 | 124 | ### Task Three (Independent Research) 125 | 126 | In your own words, explain the impact of the **Spiral** methodology on testing. Write your answers in a file called `week-10.md`. Appropriately cite your sources using **APA 7th Edition**. 127 | 128 | -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-1.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-10.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-10.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-11.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-12.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-13.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-14.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-15.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-2.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-3.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-4.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-5.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-6.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-7.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-8.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-8.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/render-9.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/render-9.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-1.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-10.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-11.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-11.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-12.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-12.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-13.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-13.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-14.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-14.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-15.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-16.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-16.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-2.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-3.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-4.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-5.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-6.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-7.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-8.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-8.png -------------------------------------------------------------------------------- /resources (ignore)/img/03/swagger-9.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/03/swagger-9.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-1.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-2.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-3.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-3.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-4.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-4.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-5.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-6.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-6.png -------------------------------------------------------------------------------- /resources (ignore)/img/04/swagger-7.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/04/swagger-7.png -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-1.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-2.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-3.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-4.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-5.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-6.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/05/swagger-7.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/05/swagger-7.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/06/capture-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/06/capture-1.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/06/capture-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/06/capture-2.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/06/capture-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/06/capture-3.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/06/capture-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/06/capture-4.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/06/capture-5.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/06/capture-5.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/06/capture-6.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/06/capture-6.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/07/capture-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/07/capture-1.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/07/capture-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/07/capture-2.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/07/capture-3.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/07/capture-3.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/07/capture-4.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/07/capture-4.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/09/helmet-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/09/helmet-1.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/09/helmet-2.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/09/helmet-2.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/09/rate-limit-1.PNG: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/09/rate-limit-1.PNG -------------------------------------------------------------------------------- /resources (ignore)/img/logo.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/img/logo.jpg -------------------------------------------------------------------------------- /resources (ignore)/marking-rurbic/practical-marking-rubric.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/marking-rurbic/practical-marking-rubric.docx -------------------------------------------------------------------------------- /resources (ignore)/marking-rurbic/project-marking-rubric.docx: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts/a4fcd08ebf906f5f9cb469022ebf5ec4ce22cd5e/resources (ignore)/marking-rurbic/project-marking-rubric.docx -------------------------------------------------------------------------------- /resources (ignore)/tex/course-directive.tex: -------------------------------------------------------------------------------- 1 | % Author: Grayson Orr 2 | % Course: ID607001: Introduction Application Development Concepts 3 | 4 | \documentclass{article} 5 | \author{} 6 | 7 | \usepackage{fontspec} 8 | \setmainfont{Arial} 9 | 10 | \usepackage{graphicx} 11 | \usepackage{wrapfig} 12 | \usepackage{enumerate} 13 | \usepackage{hyperref} 14 | \usepackage[margin = 2.25cm]{geometry} 15 | \usepackage[table]{xcolor} 16 | \hypersetup{ 17 | colorlinks = true, 18 | urlcolor = blue 19 | } 20 | \setlength\parindent{0pt} 21 | 22 | \begin{document} 23 | 24 | \begin{figure} 25 | \centering 26 | \includegraphics[width=50mm]{../img/logo.jpg} 27 | \end{figure} 28 | 29 | \title{Course Directive\\ID607001: Introductory Application Development Concepts\\Semester One, 2025} 30 | \date{} 31 | \maketitle 32 | 33 | \section*{Course Information} 34 | \begin{tabular}{ll} 35 | Level: & 6 \\ 36 | Credits: & 15 \\ 37 | Prerequisite: & ID511001: Programming 2 \\ 38 | Timetable: & Rōpū Kōwhai: Monday 1.00 PM D202 and Thursday 1.00 PM D202 \\ 39 | \end{tabular} 40 | 41 | \section*{Teaching Staff} 42 | \begin{tabular}{ll} 43 | Name: & Grayson Orr \\ 44 | Position: & Principal Lecturer and Second/Third-Year Coordinator\\ 45 | Office Location: & D309 \\ 46 | Email Address & grayson.orr@op.ac.nz \\ 47 | \end{tabular} 48 | 49 | \section*{Course Dates} 50 | \begin{tabular}{ll} 51 | Term 1 (8 weeks): & 17th February - 11th April \\ 52 | Term 2 (8 weeks): & 28th April - 19th June \\ 53 | \end{tabular} 54 | 55 | \section*{Public Holidays and Anniversary Days} 56 | A list of public holidays and anniversary days can be found here - \href{https://www.op.ac.nz/students/importantdates}{https://www.op.ac.nz/students/importantdates} 57 | 58 | \section*{Aims} 59 | To introduce the concepts of application development including algorithms, data structures and design patterns that are required to use a simple, industry-relevant development framework. 60 | 61 | \section*{Learning Outcome} 62 | At the successful completion of this course, learners will be able to: 63 | \begin{enumerate} 64 | \item Design and build secure applications with dynamic database functionality following an appropriate software development methodology. 65 | \end{enumerate} 66 | 67 | \section*{Assessments} 68 | \renewcommand{\arraystretch}{1.5} 69 | \begin{tabular}{|c|c|c|c|} 70 | \hline 71 | \textbf{Assessment} & \textbf{Weighting} & \textbf{Due Date} & \textbf{Learning Outcome} \\ \hline 72 | \small Practical & \small 20\% & \small Thursday 29th May at 2.59 PM & \small 1 \\ \hline 73 | \small Project & \small 80\% & \small Monday 9th June at 7.59 AM \small & \small 1 \\ \hline 74 | \end{tabular} 75 | 76 | \section*{Grade Table - Criterion Referenced} 77 | \renewcommand{\arraystretch}{1.5} 78 | \begin{tabular}{|c|c|} 79 | \hline 80 | \textbf{Grade} & \textbf{Mark Range} \\ \hline 81 | A+ & Met all course requirements-mark in range [90-100] \\ \hline 82 | A & Met all course requirements-mark in range [85-89] \\ \hline 83 | A- & Met all course requirements-mark in range [80-84] \\ \hline 84 | B+ & Met all course requirements-mark in range [75-79] \\ \hline 85 | B & Met all course requirements-mark in range [70-74] \\ \hline 86 | B- & Met all course requirements-mark in range [65-69] \\ \hline 87 | C+ & Met all course requirements-mark in range [60-64] \\ \hline 88 | C & Met all course requirements-mark in range [55-59] \\ \hline 89 | C- & Met all course requirements-mark in range [50-54] \\ \hline 90 | D & There at end. Did not meet course requirements. Mark in range [40-49] \\ \hline 91 | E & There at end. Did not meet course requirements. Mark in range [0-39] \\ \hline 92 | \end{tabular} 93 | 94 | \section*{Provisional Schedule} 95 | \renewcommand{\arraystretch}{1.5} 96 | \begin{tabular}{|c|c|c|} 97 | \hline 98 | \textbf{Week} & \multicolumn{2}{c|}{\textbf{Topics}} \\ \hline 99 | 1/Tahi & \multicolumn{2}{c|}{GitHub and JavaScript} \\ \hline 100 | 2/Rua & \multicolumn{2}{c|}{REST, Express, HTTP Methods, HTTP Status Codes and HTTP Headers} \\ \hline 101 | 3/Toru & \multicolumn{2}{c|}{PostgreSQL, Docker, ORM, JSDoc and Swagger} \\ \hline 102 | 4/Whā & \multicolumn{2}{c|}{API Versioning, Content Negotiation, Relationships and Repository Pattern} \\ \hline 103 | 5/Rima & \multicolumn{2}{c|}{Validation, Filtering and Sorting} \\ \hline 104 | 6/Ono & \multicolumn{2}{c|}{Seeding, API Testing and Render Deployment} \\ \hline 105 | 7/Whitu & \multicolumn{2}{c|}{Logging, Authentication and Jail} \\ \hline 106 | 107 | 8/Waru & \multicolumn{2}{c|}{Role-Based Access Control} \\ \hline 108 | \rowcolor{yellow} \multicolumn{3}{|c|}{Mid Semester Break} \\ \hline 109 | 9/Whitu & \multicolumn{2}{c|}{Rate Limiting and Securing HTTP Headers} \\ \hline 110 | 10/Tekau & \multicolumn{2}{c|}{ERD, Documentation and Software Methodologies} \\ \hline 111 | 11/Tekau mā tahi & \multicolumn{2}{c|}{Project Work and Practical Work Due} \\ \hline 112 | 12/Tekau mā rua & \multicolumn{2}{c|}{Project Work} \\ \hline 113 | 13/Tekau mā toru & \multicolumn{2}{c|}{Project Work} \\ \hline 114 | 14/Tekau mā whā & \multicolumn{2}{c|}{Project Work} \\ \hline 115 | 15/Tekau mā rima & \multicolumn{2}{c|}{Project Work Due} \\ \hline 116 | 16/Tekau mā ono & \multicolumn{2}{c|}{Catch Up Week} \\ \hline 117 | \end{tabular} 118 | 119 | \section*{Resources} 120 | 121 | \subsection*{Software} 122 | This paper will be taught using \textbf{Microsoft Visual Studio Code} and \textbf{Node.js}. An installer for \textbf{Microsoft Visual Studio Code} and \textbf{Node.js} are available - \href{https://code.visualstudio.com/download}{https://code.visualstudio.com/download} and \href{https://nodejs.org/en/download}{https://nodejs.org/en/download}. Please refer any problems with downloads or installers to \textbf{Rob Broadley} in D205a. 123 | 124 | \subsection*{Readings} 125 | No textbook is required for this course. URLs to useful resources will be provided in the lecture notes. 126 | 127 | \section*{Course Requirements and Expectations} 128 | 129 | \subsection*{Learning Hours} 130 | This course requires \textbf{150 hours} of learning. This time includes \textbf{60 hours} directed learning hours and \textbf{90} self-directed learning hours. 131 | 132 | \subsection*{Criteria for Passing} 133 | To pass this paper, you must achieve a cumulative pass mark of \textbf{50\%} over all assessments. There are no reassessments or resits. 134 | 135 | \subsection*{Attendance} 136 | \begin{itemize} 137 | \item Learners are expected to attend all classes, including lectures and labs. 138 | \item If you cannot attend for a few days for any reason, contact the course. 139 | \end{itemize} 140 | 141 | \subsection*{Communication} 142 | \textbf{Microsoft Outlook/Teams} are the official communication channels for this course. It is your responsibility to regularly check \textbf{Microsoft Outlook/Teams} and \href{https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts}{GitHub} for important course material, including changes to class scheduling or assessment details. Not checking will not be accepted as an excuse. 143 | 144 | \subsection*{Snow Days/Polytechnic Closure} 145 | In the event \textbf{Otago Polytechnic} is closed or has a delayed opening because of snow or bad weather, you should not attempt to attend class if it is unsafe to do so. It is possible that the course lecturer will not be able to attend either, so classes will not physically be meeting. However, this does not become a holiday. Rather, the course material will be made available on \href{https://github.com/otago-polytechnic-bit-courses/ID607001-intro-app-dev-concepts}{GitHub} for classes affected by the closure. You are responsible for any course material presented in this manner. Information about closure will be posted on the \textbf{Otago Polytechnic Facebook} page \href{https://www.facebook.com/OtagoPoly}{https://www.facebook.com/OtagoPoly}. 146 | 147 | \subsection*{Group Work and Originality} 148 | Learners in the \textbf{Bachelor of Information Technology} programme are expected to hand in original work. Learners are encouraged to discuss assessments with their fellow learners, however, all assessments are to be completed as individual works unless group work is explicitly required (i.e. if it doesn't say it is group work then it is not group work - even if a group consultation was involved). Failure to submit your original work will be treated as plagiarism. 149 | 150 | \subsection*{AI Tools} 151 | Learning to use AI tool is an important skill. While AI tools are powerful, you \textbf{must} be aware of the following: 152 | 153 | \begin{itemize} 154 | \item If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 155 | \item Do not trust the AI tool's responses blindly. You \textbf{must} still use your judgement and may need to do additional research to determine if the response is correct 156 | \item Acknowledge what AI tool you have used. In the assessment's repository \textbf{README.md} file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 157 | \end{itemize} 158 | 159 | \subsection*{Referencing} 160 | Appropriate referencing is required for all work. Referencing standards will be specified by the course lecturer. 161 | 162 | \subsection*{Plagiarism} 163 | Plagiarism is submitting someone elses work as your own. Plagiarism offences are taken seriously and an assessment that has been plagiarised may be awarded a zero mark. A definition of plagiarism is in the Student Handbook, available online or at the school office. 164 | 165 | \subsection*{Submission Requirements} 166 | All assessments are to be submitted by the time, date, and method given when the assessment is issued. Failure to meet all requirements will result in a penalty of up to \textbf{10\%} per day (including weekends). 167 | 168 | \subsection*{Extensions} 169 | Familiarise yourself with the assessment due dates. Extensions will \textbf{only} be granted if you are unable to complete the assessment by the due date because of \textbf{unforeseen circumstances outside your control}. The length of the extension granted will depend on the circumstances and \textbf{must} be negotiated with the course lecturer before the assessment due date. A medical certificate or support letter may be needed. Extensions will not be granted for poor time management or pressure of other assessments. 170 | 171 | \subsection*{Impairment} 172 | In case of sickness contact the course lecturer or \textbf{Head of Information Technology (Michael Holtz)} as soon as possible, preferably before the assessment is due. The policy regarding the granting of a mark that considers impaired performance requires a medical certificate and a medical practitioner’s signature on a form. You may refer to the guide on impaired performance on the student handbook. 173 | 174 | \subsection*{Appeals} 175 | If you are concerned about any aspect of your assessment, approach the course lecturer in the first instance. We support an open-door policy and aim to resolve issues promptly. Further support is available from the \textbf{Head of Information Technology (Michael Holtz)} and \textbf{Second/Third-Year Coordinator (Grayson Orr)}. \textbf{Otago Polytechnic} has a formal process for academic appeals if necessary. 176 | 177 | \subsection*{Other Documents} 178 | Regulatory documents relating to this course can be found on the \textbf{Otago Polytechnic} website. 179 | 180 | \end{document} -------------------------------------------------------------------------------- /resources (ignore)/tex/project-descriptor.tex: -------------------------------------------------------------------------------- 1 | % Author: Grayson Orr 2 | % Course: ID607001: Introductory Application Development Concepts 3 | 4 | \documentclass{article} 5 | \author{} 6 | 7 | \usepackage{fontspec} 8 | \setmainfont{Arial} 9 | 10 | \usepackage{graphicx} 11 | \usepackage{wrapfig} 12 | \usepackage{enumerate} 13 | \usepackage{hyperref} 14 | \usepackage[margin = 2.25cm]{geometry} 15 | \usepackage[table]{xcolor} 16 | \usepackage{soul} 17 | \usepackage{fancyhdr} 18 | \hypersetup{ 19 | colorlinks = true, 20 | urlcolor = blue 21 | } 22 | \setlength\parindent{0pt} 23 | \pagestyle{fancy} 24 | \fancyhf{} 25 | \rhead{College of Engineering, Construction and Living Sciences\\Bachelor of Information Technology} 26 | \lfoot{Project\\Version 4, Semester One, 2025} 27 | \rfoot{\thepage} 28 | 29 | \begin{document} 30 | 31 | \begin{figure} 32 | \centering 33 | \includegraphics[width=50mm]{../img/logo.jpg} 34 | \end{figure} 35 | 36 | \title{College of Engineering, Construction and Living Sciences\\Bachelor of Information Technology\\ID607001: Introductory Application Development Concepts\\Level 6, Credits 15\\\textbf{Project}} 37 | \date{} 38 | \maketitle 39 | 40 | \section*{Assessment Overview} 41 | In this \textbf{individual} assessment, you will develop a \textbf{REST API} using \textbf{Express} and \textbf{Node.js}, and deploy it as a \textbf{web service} on \textbf{Render}. You will choose the idea for the \textbf{REST API}. It could be related to sport, culture, food or something else you are interested in. \\ 42 | 43 | 44 | In \textbf{development} and \textbf{testing}, your data will be stored in a \textbf{PostgreSQL} database on \textbf{Docker} and in \textbf{production}, your data will be stored in a \textbf{PostgreSQL} database on \textbf{Render}. As you develop your \textbf{REST API}, you will need to write \textbf{API tests} to verify the functionality of your \textbf{REST API} using \textbf{Chai} and \textbf{Mocha}. \\ 45 | 46 | For marking purposes, provide a video demonstrating the functionality of your \textbf{REST API} and the \textbf{API tests}. Marks will also be given for code quality and best practices, documentation and \textbf{Git} usage. 47 | 48 | 49 | \section*{Learning Outcome} 50 | At the successful completion of this course, learners will be able to: 51 | \begin{enumerate} 52 | \item Design and build secure applications with dynamic database functionality following an appropriate software development methodology. 53 | \end{enumerate} 54 | 55 | \section*{Assessments} 56 | \renewcommand{\arraystretch}{1.5} 57 | \begin{tabular}{|c|c|c|c|} 58 | \hline 59 | \textbf{Assessment} & \textbf{Weighting} & \textbf{Due Date} & \textbf{Learning Outcome} \\ \hline 60 | \small Practical & \small 20\% & \small Thursday 29th May at 2.59 PM & \small 1 \\ \hline 61 | \small Project & \small 80\% & \small Monday 9th June at 7.59 AM \small & \small 1 \\ \hline 62 | \end{tabular} 63 | 64 | \section*{Conditions of Assessment} 65 | You will complete this assessment mostly during your learner-managed time. However, there will be time during class to discuss the requirements and your progress on this assessment. This assessment will need to be completed by \textbf{Monday 9th June} at \textbf{7.59 AM}. 66 | 67 | \section*{Pass Criteria} 68 | This assessment is criterion-referenced (CRA) with a cumulative pass mark of \textbf{50\%} across all assessments in \textbf{ID607001: Introductory Application Development Concepts}. 69 | 70 | \section*{Submission} 71 | You \textbf{must} submit all application files via \textbf{GitHub Classroom}. Here is the URL to the repository you will use for your submission – \href{https://classroom.github.com/a/EYdgJL1H}{https://classroom.github.com/a/EYdgJL1H}. If you do not have not one, create a \textbf{.gitignore} and add the ignored files in this resource - \href{https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore}{https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore}. Create a branch called \textbf{project}. The latest application files in the \textbf{project} branch will be used to mark against the \textbf{Functionality} criterion. Please test before you submit. Partial marks \textbf{will not} be given for incomplete functionality. Late submissions will incur a \textbf{10\% penalty per day}, rolling over at \textbf{8.00 AM}. 72 | 73 | \section*{Authenticity} 74 | All parts of your submitted assessment \textbf{must} be completely your work. Do your best to complete this assessment without using an \textbf{AI generative tool}. You need to demonstrate to the course lecturer that you can meet the learning outcome for this assessment. \\ 75 | 76 | Learning to use AI tool is an important skill. While AI tools are powerful, you \textbf{must} be aware of the following: 77 | 78 | \begin{itemize} 79 | \item If you provide an AI tool with a prompt that is not refined enough, it may generate a not-so-useful response 80 | \item Do not trust the AI tool's responses blindly. You \textbf{must} still use your judgement and may need to do additional research to determine if the response is correct 81 | \item Acknowledge what AI tool you have used. In the assessment's repository \textbf{README.md} file, please include what prompt(s) you provided to the AI tool and how you used the response(s) to help you with your work 82 | \end{itemize} 83 | 84 | It also applies to code snippets retrieved from \textbf{StackOverflow} and \textbf{GitHub}. \\ 85 | 86 | Failure to do this may result in a mark of \textbf{zero} for this assessment. 87 | 88 | \section*{Policy on Submissions, Extensions, Resubmissions and Resits} 89 | The school's process concerning submissions, extensions, resubmissions and resits complies with \textbf{Otago Polytechnic} policies. Learners can view policies on the \textbf{Otago Polytechnic} website located at \href{https://www.op.ac.nz/about-us/governance-and-management/policies}{https://www.op.ac.nz/about-us/governance-and-management/policies}. 90 | 91 | \section*{Extensions} 92 | Familiarise yourself with the assessment due date. Extensions will \textbf{only} be granted if you are unable to complete the assessment by the due date because of \textbf{unforeseen circumstances outside your control}. The length of the extension granted will depend on the circumstances and \textbf{must} be negotiated with the course lecturer before the assessment due date. A medical certificate or support letter may be needed. Extensions will not be granted on the due date and for poor time management or pressure of other assessments. 93 | 94 | \section*{Resits} 95 | Resits and reassessments are not applicable in \textbf{ID607001: Introductory Application Development Concepts}. 96 | 97 | \newpage 98 | 99 | \section*{Instructions} 100 | 101 | \subsection*{Functionality - Learning Outcome 1 (50\%)} 102 | \begin{itemize} 103 | 104 | \item Models (5\%) 105 | \begin{itemize} 106 | \item Ten models. Each model must have at least five fields. One model must be the \textbf{User} model. 107 | \item A range of data types, i.e., \textbf{Int}, \textbf{String}, \textbf{Boolean}, etc. 108 | \item Five relationships between models, i.e., one-to-one, one-to-many, many-to-many, etc. 109 | \item Three enums. Each enum must have at least three values. 110 | \end{itemize} 111 | 112 | \item Authentication and Role-Based Access Control (10\%) 113 | \begin{itemize} 114 | \item Three roles, i.e., \textbf{SUPER\_ADMIN}, \textbf{ADMIN} and \textbf{NORMAL}. 115 | \item Register a \textbf{NORMAL} user, but not a \textbf{SUPER\_ADMIN} or \textbf{ADMIN} user. 116 | \item All users can login. 117 | \item Lock a user for ten minutes after five failed login attempts. 118 | \item All users can logout. When a user logs out, the user's token is blacklisted. 119 | \end{itemize} 120 | 121 | \item CRUD Operations (15\%) 122 | \begin{itemize} 123 | \item A \textbf{repository}, \textbf{controller} and \textbf{route} file for each \textbf{model}. Each \textbf{controller} file needs to contain the following CRUD operations: \textbf{getAll}, \textbf{getById}, \textbf{create}, \textbf{update} and \textbf{delete}. 124 | 125 | \item When performing \textbf{create} and \textbf{update} operations, validate the request body. Each field must have four validation rules. 126 | 127 | \item \textbf{Filter} and \textbf{sort} data using \textbf{query parameters}. All \textbf{fields} must be filterable and sortable (in ascending and descending order). 128 | 129 | \item \textbf{Paginate} data using \textbf{query parameters}. The default number of data per page is 25. 130 | 131 | \item \textbf{SUPER\_ADMIN} Permissions 132 | \begin{itemize} 133 | \item Can perform the following operations on the \textbf{User} model: 134 | \begin{itemize} 135 | \item \textbf{getAll} and \textbf{getById} operations on all users. 136 | \item Create, update and delete \textbf{ADMIN} and \textbf{NORMAL} users, but cannot update their \textbf{password}. 137 | \item Create a \textbf{SUPER\_ADMIN} user. 138 | \item Update their data except \textbf{role}, but not other \textbf{SUPER\_ADMIN} users. 139 | \item Cannot delete other \textbf{SUPER\_ADMIN} users, including themselves. 140 | \item Disable and enable users. When a user is disabled, the user cannot login. 141 | \end{itemize} 142 | \item Perform CRUD operations on the other models. 143 | \end{itemize} 144 | 145 | \item \textbf{ADMIN} Permissions 146 | \begin{itemize} 147 | \item Can perform the following actions on the \textbf{User} model: 148 | \begin{itemize} 149 | \item \textbf{getAll} and \textbf{getById} operations on \textbf{ADMIN} and \textbf{NORMAL} users, but not \textbf{SUPER\_ADMIN} users. 150 | \item Create, update and delete \textbf{NORMAL} users, but cannot update their \textbf{password}. 151 | \item Cannot create, update and delete \textbf{SUPER\_ADMIN} and \textbf{ADMIN} users. 152 | \item Update their data except \textbf{role}, but not other \textbf{ADMIN} users. 153 | \item Cannot delete other \textbf{ADMIN} users, including themselves. 154 | \end{itemize} 155 | \item Perform CRUD operations on the other models. 156 | \end{itemize} 157 | 158 | \item \textbf{NORMAL} Permissions 159 | \begin{itemize} 160 | \item Can perform the following actions on the \textbf{User} model: 161 | \begin{itemize} 162 | \item \textbf{getAll} and \textbf{getById} operations on themselves, but not other users. 163 | \item Update their data except \textbf{role}, but not other users. 164 | \item Cannot delete other users, including themselves. 165 | \end{itemize} 166 | \item Perform \textbf{getAll} and \textbf{getById} operations on the other models. 167 | \end{itemize} 168 | \end{itemize} 169 | 170 | \item Seeding and API Tests (10\%) 171 | \begin{itemize} 172 | \item Seed five \textbf{NORMAL} users via a \textbf{CSV} file. 173 | \item Seed five \textbf{ADMIN} users via a \textbf{JSON} file. 174 | \item Seed five \textbf{SUPER\_ADMIN} users via \textbf{GitHub Gist}. 175 | \item Provide the following \textbf{API tests}: 176 | \begin{itemize} 177 | \item CRUD operations for each model. 178 | \item Filter and sort data for each model. 179 | \item Pagination for each model. 180 | \item Authentication and role-based access control. 181 | \item Limit the number of requests for CRUD operations. 182 | \end{itemize} 183 | \end{itemize} 184 | 185 | \item Logging and Security (5\%) 186 | \begin{itemize} 187 | \item Log all requests and responses in development to a file called \textbf{combined.log}. 188 | \item Secure the \textbf{X-Powered-By} header. 189 | \item Limit the number of requests for \textbf{getAll} and \textbf{getById} operations to 20 requests within a 2 minute window. Set the message to \textbf{You have exceeded the number of requests: 20. Please try again in 2 minutes.} when the limit is reached. 190 | \item Limit the number of \textbf{create}, \textbf{update} and \textbf{delete} operations to 10 requests within a minute window. Set the message to \textbf{You have exceeded the number of requests: 10. Please try again in 1 minute.} when the limit is reached. 191 | \end{itemize} 192 | 193 | \item Development, Testing and Production (5\%) 194 | \begin{itemize} 195 | \item Environment variable key's are stored in a \textbf{.env} file. 196 | \item Use \textbf{/api/v1} as the base URL for the \textbf{REST API}. 197 | \item Use \textbf{Docker} to run the \textbf{PostgreSQL} database in development and testing. 198 | \item Use \textbf{Render} to run the \textbf{PostgreSQL} database in production. 199 | \item Deploy the \textbf{REST API} as a \textbf{web service} on \textbf{Render}. 200 | \end{itemize} 201 | \end{itemize} 202 | 203 | \subsection*{Code Quality and Best Practices - Learning Outcome 1 (40\%)} 204 | \begin{itemize} 205 | \item Project Structure (10\%) - The codebase should be well-organised with a clear and logical structure that separates concerns and promotes reusability and maintainability. 206 | \item Naming Conventions (5\%) - File, class, function, variable and resource group names should be clear, descriptive and consistent across the codebase. 207 | \item Documentation and Comments (5\%) - The codebase should be well-documented using the \textbf{JSDoc} format with comments that explain complex logic and clarify the purpose of classes, functions or complex sections. 208 | \item Code Formatting (5\%) - Consistent indentation and spacing should be used across the codebase. It includes ensuring proper indentation for code blocks, following a standard format for braces and adding blank lines between sections. It should be automated using a tool like \textbf{Prettier}. 209 | \item Dead Code (5\%) - The codebase should not contain unused or redundant files, classes, functions, variables or resource groups. Keeping unnecessary codebase around can lead to confusion, clutter and potential bugs down the line. 210 | \item Performance and Scalability (10\%) - The codebase should be optimised for performance, using efficient algorithms and data structures to minimise bottlenecks. 211 | \end{itemize} 212 | 213 | \subsection*{Documentation and Git Usage - Learning Outcome 1 (10\%)} 214 | \begin{itemize} 215 | \item \textbf{GitHub} issues to help you organise and prioritise your development work. The course lecturer needs to see consistent use of \textbf{GitHub} issues for the duration of the assessment. 216 | \item Provide the following in your repository \textbf{README.md} file: 217 | \begin{itemize} 218 | \item A URL to the \textbf{REST API} as a \textbf{web service} on \textbf{Render}. 219 | \item How to setup the development environment? 220 | \item How to setup the testing environment? 221 | \item How to seed the \textbf{PostgreSQL} database? 222 | \item How to run the \textbf{API tests}? 223 | \item How do you open \textbf{Prisma Studio}? 224 | \item An \textbf{ERD} of your \textbf{REST API}. 225 | \item A URL to the video demonstrating the functionality of the \textbf{REST API} and the \textbf{API tests}. 226 | \end{itemize} 227 | \item Use of \textbf{Markdown}, i.e., headings, bold text, code blocks, etc. 228 | \item Correct spelling and grammar. 229 | \item A \textbf{.gitignore} file containing the ignored files in this resource - \href{https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore}{https://raw.githubusercontent.com/github/gitignore/main/Node.gitignore}. 230 | \item Your \textbf{Git commit messages} should: 231 | \begin{itemize} 232 | \item Reflect the context of each functional requirement change. 233 | \item Be formatted using an appropriate naming convention style. 234 | \end{itemize} 235 | \end{itemize} 236 | 237 | \subsection*{Additional Information} 238 | \begin{itemize} 239 | \item Your \textbf{REST API} idea must be signed off by the course lecturer before you start your development work. 240 | \item If you \textbf{do not} provide a video demonstrating the functionality of your \textbf{REST API} and the \textbf{API tests}, you will receive a mark of \textbf{zero} for this assessment. 241 | \item \textbf{Do not} rewrite your \textbf{Git} history. It is important that the course lecturer can see how you worked on your assessment over time. 242 | \item You need to show the course lecturer the initial \textbf{GitHub} issues before you start your development work. Following this, you need to show the course lecturer your \textbf{GitHub} issues at the end of each week. 243 | \end{itemize} 244 | \end{document} --------------------------------------------------------------------------------