Skip to content
Permalink
Browse files
updated lab sheet
  • Loading branch information
aa7401 committed Oct 7, 2019
1 parent c593b3e commit 53fc0b6f10d1c83b51cde6d02f5a0573dd014a68
Show file tree
Hide file tree
Showing 18 changed files with 278 additions and 83 deletions.
@@ -10,43 +10,49 @@ In this worksheet you will be applying a range of techniques to improve the qual

Before you start you need to pull any _upstream changes_. Detailed instructions can be found in the **Setup** lab.

## 1 Modularisation
## 1 The Package Manifest

The first step you will need to do is to split your code up to make it easier to understand. Take a look at the `06_code_quality/login` project.
Up until now you have needed to install each NodeJS package separately however most projects you will see from now onwards include a **package manifest** whick contains the project metadata. In a NodeJS project this file is named `package.json`. Locate the `06_code_quality/todo/` directory and open the `package.json` file it contains. This is a JSON file that contains a number of properties that are used by the application.

Notice the line where we import the module.
Locate the `dependencies` property, notice that this lists a number of packages that are needed by the application. Rather than install depndencies one by one we can tell the package manager to install all the packages listed here.

```javascript
const accounts = require('modules/accounts')
```bash
$ npm install
```

This loads the module into a constant called `accounts`.
Notice that this has installed all these listed packages. The manifest also specifies which version of each package are installed. If you want to install additional packages you can get these added to the `package.json` file automatically. For example if we want to install the `http-status-codes` package this should be done like this:

```bash
$ npm install --save http-status-codes
```

Now locate the second `router.post('/login')` route (this is currently commented out). Comment out the one you have been using and uncomment this new shorter version. If you run your server and test it you will find the functionality is identical. Its time to understand how this new version works:
## 2 Modularisation

1. We start by storing the `request.body` data (the HTTP POST request body data) in an immutable variable called `body`.
2. Next we call the `checkCredentials()` function that we imported from the `accounts.js` module passing the username and password as parameters.
3. If this function does not throw an exception it means the credentials are valid so we set the cookie and redirect to the home page.
4. If either the username or password are invalid, the `checkCredentials()` function will throw an exception which will be handled by the `catch()` block. The error message will explain what us wrong so we pass this back to the login page.
Next you will need to do is to split your code up to make it easier to understand. Take a look at the `06_code_quality/todo/` project. If you run this you will see that it is a simple shopping list app where you can add multiple items and quantities. Currently all the functionality is contained in the `index.js` file. Locate the `modules/list.js` file. This declares a new Object Prototype called `List` which includes all the necessary functionality for the app. At the moment this is not being used by the app.

Now we need to look at the `accounts.js` module. This implements some very important concepts that you will need to understand and apply to your assignment.
Lets examine this module:

1. The `accounts` module contains two types of function:
1. The [function declarations](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/function) such as `runSQL()` have a private scope and are not visible outside the module.
2. Any [function expressions](https://developer.mozilla.org/en-US/docs/web/JavaScript/Reference/Operators/function) stored as keys in the `module.exports` object such as `module.exports.checkCredentials` are available to any code that imports this module.
2. All the code in a function is wrapped in a [try-catch](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/try...catch) block to handle any exceptions.
3. The catch block simple propagates the [Error](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Error) object to the calling code.
4. Any non-valid code flow is handled by throwing an Error object which forces the code flow to jump directly to the catch block.
5. This means that if the program flow reaches the end of the try block everything was successful and data can be returned.
1. The `module.exports` object is used to define what functionality will be made available to our `index.js` file. In this case we are exporting the Object Prototype. Notice we are using the new `Class` constructor.
2. Lines 6-8 are the constructor where we define our array that can be accessed by the object.
3. The rest of the file defines a series of functions that form part of the object prototype.

### 1.1 Test Your Understanding
Now look at the top of the `index.js` file. Lines 22-23 import our module into the `ToDo` variable. This can then be used to create a new object using our `ToDo` object prototype. This object is called `todo` and provides access to all the functionality defined in the object prototype. Foe example to add an item we can use:

```javascript
todo.add('bread', 42)
```

This will call the `add()` function that is part of our `todo` object prototype.

### 2.1 Test Your Understanding

To check you understand how to use modules you are now expected to move more of the functionality from the `index.js` file into this separate module. To help you with this you will find stub functions that only require the functionality to be added! You will be modifying the functions below the `/* --- STUB FUNCTIONS --- */` line.
The custom object prototype defined in the `list.js` module already contains the functionality needed by your app.

1. Implement the `checkNoDuplicateUsername(username)` function to comply with the JSDoc comments, it should throw an exception if a duplicate user is found or `true` if not.
2. Implement the `saveImage()` function. This should check that the image is of the correct type then save it to the `avatars/` directory.
3. Now implement the `addUser()` function. This should make use of the functions you created in the first two tasks. It should check for duplicates before saving the image then it should encrypting the password and then saving the new record.
4. The final step is to comment out the `router.post('register')` route in `index.js` then create a replacement that makes use of the functions you added to the `accounts.js` module.
1. Uncomment lines 22-23 to import the module and create a custom object.
2. In the `router.post('/')` function call replace lines 41-42 with a call to `todo.add()`, passing the item name and quantity as parameters.
3. Now modify the `router.get('/')` function callback by replacing lines 29-30 with a call to the `todo.getAll()` function.
4. To test the functionality so far, comment out the array declaration on line 20 and try starting the web server. You should be able to add items, the data is now stored in the custom object.
5. Finally replace line 53 with a call to the appropriate function in the custom object.

Now much of the business logic has been moved to the separate module, are there any module imports in `index.js` that are no longer needed? Locate these and delete.

This file was deleted.

File renamed without changes.
@@ -0,0 +1,28 @@

'use strict'

module.exports = class List {

constructor() {
this.items = []
}

add(item, qty) {
const data = {item: item, qty: qty}
this.items.push(data)
}

getAll() {
return this.items
}

delete(name) {
const index = this.items.indexOf(name)
if(index !== -1) this.items.splice(index, 1)
}

count() {
return this.items.count
}

}
Empty file.
@@ -0,0 +1,57 @@
{
"name": "10_auth",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"acceptance": "jest --coverage --detectOpenHandles",
"jsdoc": "node_modules/.bin/jsdoc -c jsdoc.conf",
"linter": "node_modules/.bin/eslint .",
"test": "jest --coverage --detectOpenHandles",
"unit": "node_modules/.bin/jest --coverage --runInBand tests/unit/"
},
"jest": {
"testEnvironment": "node",
"verbose": true,
"collectCoverage": true,
"coverageDirectory": "docs/coverage/",
"coverageThreshold": {
"global": {
"branches": 0,
"functions": 0,
"lines": 0,
"statements": 0
}
}
},
"author": "",
"license": "ISC",
"dependencies": {
"bcrypt": "^3.0.3",
"bcrypt-promise": "^2.0.0",
"fs-extra": "^7.0.1",
"handlebars": "^4.1.2",
"jimp": "^0.6.0",
"koa": "^2.6.2",
"koa-body": "^4.0.8",
"koa-bodyparser": "^4.2.1",
"koa-router": "^7.4.0",
"koa-session": "^5.10.1",
"koa-static": "^5.0.0",
"koa-views": "^6.1.5",
"mime-types": "^2.1.22",
"sqlite-async": "^1.0.11"
},
"devDependencies": {
"eslint": "^5.15.2",
"handlebars-validate": "^0.1.2",
"html-validator-cli": "^6.0.0",
"http-status-codes": "^1.3.2",
"jest": "^24.1.0",
"jsdoc": "^3.5.5",
"jsdoc-route-plugin": "^0.1.0",
"puppeteer": "^1.12.2",
"site-validator-cli": "^1.0.1",
"supertest": "^4.0.2"
}
}
@@ -0,0 +1,61 @@
#!/usr/bin/env node

'use strict'

const Koa = require('koa')
const Router = require('koa-router')
const stat = require('koa-static')
const bodyParser = require('koa-bodyparser')
const handlebars = require('koa-hbs-renderer')

const app = new Koa()
const router = new Router()
app.use(stat('public'))
app.use(bodyParser())
app.use(handlebars({ paths: { views: `${__dirname}/views` } }))
app.use(router.routes())

const port = 8080

const items = []

// const ToDo = require('./modules/todo')
// const todo = new ToDo()

router.get('/', async ctx => {
try {
const data = {}
if(ctx.query.msg) data.msg = ctx.query.msg
data.items = items.map( (element, index) => ({key: index, item: element.item, qty: element.qty}))
console.log(data.items)
ctx.render('home', data)
} catch(err) {
console.log(err.message)
ctx.render('home', {msg: err.message})
}
})

router.post('/', ctx => {
try {
const body = ctx.request.body
const data = {item: body.item, qty: body.qty}
items.push(data)
ctx.redirect('/')
} catch(err) {
console.log(err.message)
ctx.redirect(`/?msg=${err.message}`)
}
})

router.get('/delete/:key', ctx => {
try {
console.log(`key: ${ctx.params.key}`)
items.splice(ctx.params.key, 1)
ctx.redirect('/msg=item deleted')
} catch(err) {
console.log(err.message)
ctx.redirect(`/${err.message}`)
}
})

module.exports = app.listen(port, () => console.log(`listening on port ${port}`))
@@ -0,0 +1,27 @@

'use strict'

module.exports = class List {

constructor() {
this.items = []
}

add(item, qty) {
const data = {item: item, qty: qty}
this.items.push(data)
}

getAll() {
return this.items.map( (element, index) => ({key: index, item: element.item, qty: element.qty}))
}

delete(id) {
this.items.splice(id, 1)
}

count() {
return this.items.count
}

}
@@ -0,0 +1,22 @@
{
"name": "todo",
"version": "1.0.0",
"description": "",
"main": "index.js",
"scripts": {
"linter": "node_modules/.bin/eslint ."
},
"author": "",
"license": "ISC",
"dependencies": {
"handlebars": "^4.2.0",
"koa": "^2.8.1",
"koa-bodyparser": "^4.2.1",
"koa-handlebars": "^1.0.0",
"koa-hbs-renderer": "^1.2.0",
"koa-router": "^7.4.0",
"koa-static": "^5.0.0",
"koa-views": "^6.2.1"
},
"devDependencies": {}
}
@@ -0,0 +1,19 @@

<!DOCTYPE html>
<html>
<head>
<title>ToDo List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>ToDo List</h1>
<h2>Your list is empty, lets add some items...</h2>
<form method="post" action="/">
Item:
<input type="text" name="item" />
Qty:
<input type="text" name="qty" />
<input type="submit" value="Add Item" />
</form>
</body>
</html>
@@ -0,0 +1,33 @@

<!DOCTYPE html>
<html>
<head>
<title>ToDo List</title>
<link rel="stylesheet" href="style.css" />
</head>
<body>
<h1>ToDo List</h1>
<h2>My List</h2>
{{#if msg}}
<p class="msg">{{msg}}</p>
{{/if}}
{{#if items.length}}
<table>
<caption>Shopping List</caption>
<tr><th>Item</th><th>Qty</th><th>Action</th></tr>
{{#each items}}
<tr><td>{{this.item}}</td><td>{{this.qty}}</td><td><a href="/delete/{{this.key}}">delete</a></td></tr>
{{/each}}
</table>
{{else}}
<h2>Your list is empty, lets add some items...</h2>
{{/if}}
<form method="post" action="/">
Item:
<input type="text" name="item" />
Qty:
<input type="text" name="qty" />
<input type="submit" value="Add Item" />
</form>
</body>
</html>

0 comments on commit 53fc0b6

Please sign in to comment.