forked from web/foundation
Permalink
Show file tree
Hide file tree
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Showing
11 changed files
with
248 additions
and
94 deletions.
There are no files selected for viewing
122
exercises/07_unit_testing/todo/index.js
100755 → 100644
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,90 +1,54 @@ | ||
#!/usr/bin/env node | ||
/* eslint no-magic-numbers: 0 */ | ||
|
||
'use strict' | ||
|
||
/* import the 'restify' module and create an instance. */ | ||
const restify = require('restify') | ||
const server = restify.createServer() | ||
|
||
/* import the required plugins to parse the body and auth header. */ | ||
server.use(restify.fullResponse()) | ||
server.use(restify.bodyParser()) | ||
server.use(restify.authorizationParser()) | ||
|
||
/* import our custom module. */ | ||
const lists = require('./lists.js') | ||
|
||
/* if we receive a GET request for the base URL redirect to /lists */ | ||
server.get('/', (req, res, next) => { | ||
res.redirect('/lists', next) | ||
}) | ||
|
||
/* this route provides a URL for the 'lists' collection. It demonstrates how a single resource/collection can have multiple representations. */ | ||
server.get('/lists', (req, res) => { | ||
console.log('getting a list of all the lists') | ||
/* we will be including URLs to link to other routes so we need the name of the host. Notice also that we are using an 'immutable variable' (constant) to store the host string since the value won't change once assigned. The 'const' keyword is new to ECMA6 and is supported in NodeJS. */ | ||
const host = req.headers.host | ||
console.log(host) | ||
/* creating some empty variables */ | ||
let data //type | ||
/* is the client requesting xml data? The req.header object stores any headers passed in the request. The 'Accept' header lets the client express a preference for the format of the representation. Note you should always provide a sensible default. */ | ||
if (req.header('Accept') === 'application/xml') { | ||
data = lists.getAllXML(host) | ||
} else { | ||
data = lists.getAll(host) | ||
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 todo = require('./modules/todo') | ||
|
||
router.get('/', async ctx => { | ||
try { | ||
const data = todo.getAll() | ||
ctx.render('home', {items: data}) | ||
} catch(err) { | ||
console.log(err.message) | ||
ctx.render('empty') | ||
} | ||
/* we need to set the content-type to match the data we are sending. We then send the response code and body. Finally we signal the end of the response. */ | ||
res.setHeader('content-type', data.contentType) | ||
res.send(data.code, data.response) | ||
res.end() | ||
}) | ||
|
||
/* This route provides a URL for each list resource. It includes a parameter (indicated by a :). The string entered here is stored in the req.params object and can be used by the script. */ | ||
server.get('/lists/:listID', (req, res) => { | ||
console.log('getting a list based on its ID') | ||
/* Here we store the id we want to retrieve in an 'immutable variable'. */ | ||
const listID = req.params.listID | ||
/* Notice that all the business logic is kept in the 'lists' module. This stops the route file from becoming cluttered and allows us to implement 'unit testing' (we cover this in a later topic) */ | ||
const data = lists.getByID(listID) | ||
res.setHeader('content-type', 'application/json') | ||
res.send(data.code, data.response) | ||
res.end() | ||
}) | ||
|
||
/* This route points to the 'lists' collection. The POST method indicates that we indend to add a new resource to the collection. Any resource added to a collection using POST should be assigned a unique id by the server. This id should be returned in the response body. */ | ||
server.post('/lists', (req, res) => { | ||
console.log('adding a new list') | ||
/* The req object contains all the data associated with the request received from the client. The 'body' property contains the request body as a string. */ | ||
const body = req.body | ||
/* Since we are using the authorization parser plugin we gain an additional object which contains the information from the 'Authorization' header extracted into useful information. Here we are displaying it in the console so you can understand its structure. */ | ||
const auth = req.authorization | ||
console.log(auth) | ||
const data = lists.addNew(auth, body) | ||
res.setHeader('content-type', data.contentType) | ||
res.send(data.code, data.response) | ||
res.end() | ||
}) | ||
|
||
/* The PUT method is used to 'update' a named resource. This is not only used to update a named resource that already exists but is also used to create a NEW RESOURCE at the named URL. It's important that you understand how this differs from a POST request. */ | ||
server.put('/lists/:listID', (req, res) => { | ||
res.setHeader('content-type', 'application/json') | ||
//res.send(data.code, {status: data.status, message: 'this should update the specified resource'}) | ||
res.end() | ||
}) | ||
|
||
/* The DELETE method removes the resource at the specified URL. */ | ||
server.del('/lists/:listID', (req, res) => { | ||
res.setHeader('content-type', 'application/json') | ||
//res.send(data.code, {status: data.status, message: 'this should delete the specified resource'}) | ||
res.end() | ||
router.post('/', ctx => { | ||
try { | ||
const body = ctx.request.body | ||
todo.add(body.item, body.qty) | ||
} catch(err) { | ||
console.log(err.message) | ||
} finally { | ||
ctx.redirect('/') | ||
} | ||
}) | ||
|
||
const port = process.env.PORT || 8080 | ||
server.listen(port, err => { | ||
if (err) { | ||
console.error(err) | ||
} else { | ||
console.log('App is ready at : ' + port) | ||
router.get('/delete/:key', ctx => { | ||
try { | ||
console.log(`key: ${ctx.params.key}`) | ||
todo.delete(ctx.params.key) | ||
} catch(err) { | ||
console.log(err.message) | ||
} finally { | ||
ctx.redirect('/') | ||
} | ||
}) | ||
|
||
module.exports = app.listen(port, () => console.log(`listening on port ${port}`)) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,23 @@ | ||
|
||
'use strict' | ||
|
||
let data = [] | ||
|
||
module.exports.clear = () => { | ||
data = [] | ||
} | ||
|
||
module.exports.add = (item, qty) => { | ||
data.push({item: item, qty: qty}) | ||
} | ||
|
||
module.exports.getAll = () => { | ||
if(data.length === 0) throw new Error('empty list') | ||
for(const key in data) data[key].key = key | ||
return data | ||
} | ||
|
||
module.exports.delete = key => { | ||
console.log(`delete key ${key}`) | ||
return | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,25 +1,21 @@ | ||
{ | ||
"name": "todo", | ||
"version": "1.0.0", | ||
"description": "Simple API to maintain to do lists.", | ||
"description": "", | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "node index.js" | ||
"test": "echo \"Error: no test specified\" && exit 1" | ||
}, | ||
"keywords": [ | ||
"api", | ||
"restify", | ||
"tutorial" | ||
], | ||
"author": "Mark J Tyers", | ||
"author": "", | ||
"license": "ISC", | ||
"dependencies": { | ||
"csprng": "^0.1.1", | ||
"restify": "^4.0.3", | ||
"xmlbuilder": "^3.1.0" | ||
}, | ||
"devDependencies": { | ||
"frisby": "^0.8.5", | ||
"jasmine-node": "^1.14.5" | ||
"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" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
|
||
body { | ||
font-family: Arial, Helvetica, sans-serif; | ||
} | ||
|
||
.msg { | ||
border: 1px solid red; | ||
font-weight: bold; | ||
color: red; | ||
padding: 1em; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
|
||
<!DOCTYPE html> | ||
<html> | ||
<head> | ||
<title>ToDo List</title> | ||
<link rel="stylesheet" href="style.css" /> | ||
</head> | ||
<body> | ||
<h1>ToDo List</h1> | ||
<h2>My List</h2> | ||
<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> | ||
<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> |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,90 @@ | ||
#!/usr/bin/env node | ||
/* eslint no-magic-numbers: 0 */ | ||
|
||
'use strict' | ||
|
||
/* import the 'restify' module and create an instance. */ | ||
const restify = require('restify') | ||
const server = restify.createServer() | ||
|
||
/* import the required plugins to parse the body and auth header. */ | ||
server.use(restify.fullResponse()) | ||
server.use(restify.bodyParser()) | ||
server.use(restify.authorizationParser()) | ||
|
||
/* import our custom module. */ | ||
const lists = require('./lists.js') | ||
|
||
/* if we receive a GET request for the base URL redirect to /lists */ | ||
server.get('/', (req, res, next) => { | ||
res.redirect('/lists', next) | ||
}) | ||
|
||
/* this route provides a URL for the 'lists' collection. It demonstrates how a single resource/collection can have multiple representations. */ | ||
server.get('/lists', (req, res) => { | ||
console.log('getting a list of all the lists') | ||
/* we will be including URLs to link to other routes so we need the name of the host. Notice also that we are using an 'immutable variable' (constant) to store the host string since the value won't change once assigned. The 'const' keyword is new to ECMA6 and is supported in NodeJS. */ | ||
const host = req.headers.host | ||
console.log(host) | ||
/* creating some empty variables */ | ||
let data //type | ||
/* is the client requesting xml data? The req.header object stores any headers passed in the request. The 'Accept' header lets the client express a preference for the format of the representation. Note you should always provide a sensible default. */ | ||
if (req.header('Accept') === 'application/xml') { | ||
data = lists.getAllXML(host) | ||
} else { | ||
data = lists.getAll(host) | ||
} | ||
/* we need to set the content-type to match the data we are sending. We then send the response code and body. Finally we signal the end of the response. */ | ||
res.setHeader('content-type', data.contentType) | ||
res.send(data.code, data.response) | ||
res.end() | ||
}) | ||
|
||
/* This route provides a URL for each list resource. It includes a parameter (indicated by a :). The string entered here is stored in the req.params object and can be used by the script. */ | ||
server.get('/lists/:listID', (req, res) => { | ||
console.log('getting a list based on its ID') | ||
/* Here we store the id we want to retrieve in an 'immutable variable'. */ | ||
const listID = req.params.listID | ||
/* Notice that all the business logic is kept in the 'lists' module. This stops the route file from becoming cluttered and allows us to implement 'unit testing' (we cover this in a later topic) */ | ||
const data = lists.getByID(listID) | ||
res.setHeader('content-type', 'application/json') | ||
res.send(data.code, data.response) | ||
res.end() | ||
}) | ||
|
||
/* This route points to the 'lists' collection. The POST method indicates that we indend to add a new resource to the collection. Any resource added to a collection using POST should be assigned a unique id by the server. This id should be returned in the response body. */ | ||
server.post('/lists', (req, res) => { | ||
console.log('adding a new list') | ||
/* The req object contains all the data associated with the request received from the client. The 'body' property contains the request body as a string. */ | ||
const body = req.body | ||
/* Since we are using the authorization parser plugin we gain an additional object which contains the information from the 'Authorization' header extracted into useful information. Here we are displaying it in the console so you can understand its structure. */ | ||
const auth = req.authorization | ||
console.log(auth) | ||
const data = lists.addNew(auth, body) | ||
res.setHeader('content-type', data.contentType) | ||
res.send(data.code, data.response) | ||
res.end() | ||
}) | ||
|
||
/* The PUT method is used to 'update' a named resource. This is not only used to update a named resource that already exists but is also used to create a NEW RESOURCE at the named URL. It's important that you understand how this differs from a POST request. */ | ||
server.put('/lists/:listID', (req, res) => { | ||
res.setHeader('content-type', 'application/json') | ||
//res.send(data.code, {status: data.status, message: 'this should update the specified resource'}) | ||
res.end() | ||
}) | ||
|
||
/* The DELETE method removes the resource at the specified URL. */ | ||
server.del('/lists/:listID', (req, res) => { | ||
res.setHeader('content-type', 'application/json') | ||
//res.send(data.code, {status: data.status, message: 'this should delete the specified resource'}) | ||
res.end() | ||
}) | ||
|
||
const port = process.env.PORT || 8080 | ||
server.listen(port, err => { | ||
if (err) { | ||
console.error(err) | ||
} else { | ||
console.log('App is ready at : ' + port) | ||
} | ||
}) |
File renamed without changes.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
{ | ||
"name": "todo", | ||
"version": "1.0.0", | ||
"description": "Simple API to maintain to do lists.", | ||
"main": "index.js", | ||
"scripts": { | ||
"start": "node index.js" | ||
}, | ||
"keywords": [ | ||
"api", | ||
"restify", | ||
"tutorial" | ||
], | ||
"author": "Mark J Tyers", | ||
"license": "ISC", | ||
"dependencies": { | ||
"csprng": "^0.1.1", | ||
"restify": "^4.0.3", | ||
"xmlbuilder": "^3.1.0" | ||
}, | ||
"devDependencies": { | ||
"frisby": "^0.8.5", | ||
"jasmine-node": "^1.14.5" | ||
} | ||
} |
File renamed without changes.