HW7: done task 1 and 2
This commit is contained in:
parent
a73e47f83a
commit
243b1f5f6a
|
@ -0,0 +1,51 @@
|
|||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const logger = require('morgan');
|
||||
const bodyParser = require('body-parser');
|
||||
const kleiDust = require('klei-dust');
|
||||
const methodOverride = require('method-override');
|
||||
const fs = require('fs');
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
mongoose.connect('mongodb://localhost/SA3_hw6', {
|
||||
useNewUrlParser: true,
|
||||
useUnifiedTopology: true,
|
||||
});
|
||||
|
||||
require('./models/Favorites');
|
||||
|
||||
const app = express();
|
||||
|
||||
//configure app
|
||||
app.use(logger('dev'));
|
||||
app.set('views', __dirname + '/views');
|
||||
app.engine('dust', kleiDust.dust);
|
||||
app.set('view engine', 'dust');
|
||||
app.set('view options', { layout: false });
|
||||
|
||||
app.use(methodOverride('_method'));
|
||||
|
||||
// parse application/x-www-form-urlencoded
|
||||
app.use(bodyParser.urlencoded({ extended: false }));
|
||||
|
||||
// parse application/json
|
||||
app.use(bodyParser.json());
|
||||
|
||||
// Needed to make fetch_tests passs
|
||||
app.use(bodyParser.text({ type: '*/*' }));
|
||||
|
||||
app.use(express.static('public'));
|
||||
|
||||
|
||||
// Initialize routers here
|
||||
const routers = require('./routes/routers');
|
||||
app.use('/', routers.root);
|
||||
// app.use('/favorites', routers.favourites_db);
|
||||
// app.use('/favorites', routers.favourites_db_promises);
|
||||
app.use('/favorites', routers.favourites_db_asaw);
|
||||
app.use('/bookmarked', routers.bookmarked);
|
||||
app.use('/test/fetch', routers.fetch_tests);
|
||||
|
||||
module.exports = app;
|
|
@ -0,0 +1,9 @@
|
|||
#!/usr/bin/env node
|
||||
var debug = require('debug')('canvas-server');
|
||||
var app = require('../app');
|
||||
|
||||
app.set('port', process.env.PORT || 3000);
|
||||
|
||||
var server = app.listen(app.get('port'), function() {
|
||||
debug('Express server listening on port ' + server.address().port);
|
||||
});
|
|
@ -0,0 +1,11 @@
|
|||
module.exports = {
|
||||
//Server URL
|
||||
url: "http://127.0.0.1:3000",
|
||||
//Form structure
|
||||
form: {
|
||||
_id: "_id",
|
||||
name: "name",
|
||||
dataURL: "dataURL",
|
||||
bookmarked: "bookmarked" // Optional
|
||||
}
|
||||
}
|
|
@ -0,0 +1,20 @@
|
|||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
const schema = new mongoose.Schema({
|
||||
_id: {},
|
||||
dataURL: { type: String, required: true },
|
||||
name: { type: String, required: true, default: '' },
|
||||
bookmarked: {
|
||||
type: Boolean,
|
||||
required: false,
|
||||
default: false,
|
||||
},
|
||||
dateCreated: { type: Date, required: true, default: Date.now }
|
||||
});
|
||||
|
||||
const Favorite = mongoose.model('Favorite', schema);
|
||||
|
||||
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
{
|
||||
"name": "wa-exercise-7-2017-2018",
|
||||
"version": "0.0.0",
|
||||
"description": "Exercise 7 of Web Atelier. Express.js and Mongoose",
|
||||
"main": "app.js",
|
||||
"scripts": {
|
||||
"start": "DEBUG='canvas-server' nodemon ./bin/www",
|
||||
"test": "npm run test-mocha",
|
||||
"test-mocha": "./node_modules/mocha/bin/mocha -R spec ./test/routes ./test/hypermedia"
|
||||
},
|
||||
"keywords": [
|
||||
"Node.js",
|
||||
"Express.js",
|
||||
"MongoDB",
|
||||
"Mongoose",
|
||||
"REST"
|
||||
],
|
||||
"author": "Vincenzo Ferme, Andrea Gallidabino, Vasileios Triglianos, Ilya Yanok",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"body-parser": "^1.19.0",
|
||||
"debug": "^3.1.0",
|
||||
"dustjs-linkedin": "^2.7.5",
|
||||
"express": "^4.16.2",
|
||||
"klei-dust": "^1.0.0",
|
||||
"method-override": "^3.0.0",
|
||||
"mongoose": "^5.7.7",
|
||||
"morgan": "^1.9.0",
|
||||
"request": "^2.88.0",
|
||||
"supertest": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mocha": "^4.0.1",
|
||||
"should": "^13.1.3"
|
||||
}
|
||||
}
|
|
@ -0,0 +1,86 @@
|
|||
/* Fetch */
|
||||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const BODY_METHODS = ['PUT', 'POST', 'PATCH'];
|
||||
const NON_BODY_METHODS = ['GET', 'OPTIONS', 'HEAD', 'DELETE'];
|
||||
const METHODS = BODY_METHODS + NON_BODY_METHODS;
|
||||
|
||||
/**
|
||||
* @function doFetchRequest
|
||||
* @param {String} method The method of the Fetch request. One of: 'GET',
|
||||
* 'POST', 'PUT', 'DELETE', 'PATCH', 'OPTIONS', 'HEAD'
|
||||
* @param {String} url The url of the API to call, optionally with parameters.
|
||||
* @param {Object} headers The Associative Array containing the Request Headers.
|
||||
* It must be undefined if there are no headers.
|
||||
* @param {String} body The body String to be sent to the server. It must be
|
||||
* undefined if there is no body.
|
||||
* @returns {Promise} which receives the HTTP response.
|
||||
*/
|
||||
function doFetchRequest(method, url, headers, body) {
|
||||
if (METHODS.indexOf(method) == -1) {
|
||||
throw new Error(`${method} is not a method`);
|
||||
return;
|
||||
}
|
||||
|
||||
if (BODY_METHODS.indexOf(method) != -1 && typeof body != 'string') {
|
||||
throw new Error(`body must be a string with ${BODY_METHODS}`);
|
||||
return;
|
||||
} else if (NON_BODY_METHODS.indexOf(method) != -1 && body !== undefined) {
|
||||
throw new Error(`body must be undefined with ${NON_BODY_METHODS}`);
|
||||
return;
|
||||
}
|
||||
|
||||
return fetch(url, {
|
||||
method: method,
|
||||
headers: headers,
|
||||
body: body
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
/** @function doJSONRequest
|
||||
* @param {String} method The method of the Fetch request. One of: 'GET',
|
||||
* 'POST', 'PUT', 'DELETE'.
|
||||
* @param {String} url The url of the API to call, optionally with parameters.
|
||||
* @param {Object} headers The Associative Array containing the Request Headers.
|
||||
* It must be undefined if there are no headers.
|
||||
* @param {Object} data The object to be sent as JSON body to the server.
|
||||
* It must be undefined if there is no body.
|
||||
* @returns {Promise} which receives directly the object parsed from the
|
||||
* response JSON.
|
||||
*/
|
||||
function doJSONRequest(method, url, headers, data){
|
||||
const JSON_MIME = 'application/json';
|
||||
|
||||
headers = Object.assign({}, headers);
|
||||
|
||||
if (('Content-Type' in headers && headers['Content-Type'] !== JSON_MIME) ||
|
||||
('Accept' in headers && headers['Accept'] !== JSON_MIME)) {
|
||||
throw new Error(`headers object contains Content-Type or Accept`);
|
||||
return;
|
||||
}
|
||||
|
||||
headers['Accept'] = JSON_MIME;
|
||||
|
||||
if (NON_BODY_METHODS.indexOf(method) != -1 && data !== undefined) {
|
||||
throw new Error(`data must be undefined with ${NON_BODY_METHODS}`);
|
||||
return;
|
||||
} else if (BODY_METHODS.indexOf(method) != -1) {
|
||||
if (typeof data != 'object' || data === null) {
|
||||
throw new Error(`data must be a non-null object with ${BODY_METHODS}`);
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
data = JSON.stringify(data);
|
||||
} catch (e) {
|
||||
throw new Error('data cannot be converted into JSON: ' + e);
|
||||
return;
|
||||
}
|
||||
|
||||
headers['Content-Type'] = JSON_MIME;
|
||||
}
|
||||
|
||||
return doFetchRequest(method, url, headers, data).then(res => res.json());
|
||||
}
|
||||
|
|
@ -0,0 +1,15 @@
|
|||
// vim: set ts=2 sw=2 tw=80 et:
|
||||
// Enter your initialization code here
|
||||
|
||||
function init() {
|
||||
// Create canvas app
|
||||
const app = new App({
|
||||
canvas: 'canvas',
|
||||
buttons: {
|
||||
clear: 'clear-btn',
|
||||
camera: 'camera-btn',
|
||||
undo: 'undo-btn'
|
||||
},
|
||||
brushToolbar: 'brush-toolbar'
|
||||
});
|
||||
}
|
|
@ -0,0 +1,5 @@
|
|||
let test = QUnit.test;
|
||||
let equal = QUnit.assert.equal.bind(QUnit.assert);
|
||||
let notEqual = QUnit.assert.notEqual.bind(QUnit.assert);
|
||||
let deepEqual = QUnit.assert.deepEqual.bind(QUnit.assert);
|
||||
let notDeepEqual = QUnit.assert.notDeepEqual.bind(QUnit.assert);
|
|
@ -0,0 +1,436 @@
|
|||
/*!
|
||||
* QUnit 2.9.2
|
||||
* https://qunitjs.com/
|
||||
*
|
||||
* Copyright jQuery Foundation and other contributors
|
||||
* Released under the MIT license
|
||||
* https://jquery.org/license
|
||||
*
|
||||
* Date: 2019-02-21T22:49Z
|
||||
*/
|
||||
|
||||
/** Font Family and Sizes */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult {
|
||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||
#qunit-tests { font-size: smaller; }
|
||||
|
||||
|
||||
/** Resets */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-filteredTest, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/** Header (excluding toolbar) */
|
||||
|
||||
#qunit-header {
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
|
||||
color: #8699A4;
|
||||
background-color: #0D3349;
|
||||
|
||||
font-size: 1.5em;
|
||||
line-height: 1em;
|
||||
font-weight: 400;
|
||||
|
||||
border-radius: 5px 5px 0 0;
|
||||
}
|
||||
|
||||
#qunit-header a {
|
||||
text-decoration: none;
|
||||
color: #C2CCD1;
|
||||
}
|
||||
|
||||
#qunit-header a:hover,
|
||||
#qunit-header a:focus {
|
||||
color: #FFF;
|
||||
}
|
||||
|
||||
#qunit-banner {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
#qunit-filteredTest {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
color: #366097;
|
||||
background-color: #F4FF77;
|
||||
}
|
||||
|
||||
#qunit-userAgent {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
color: #FFF;
|
||||
background-color: #2B81AF;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
|
||||
|
||||
/** Toolbar */
|
||||
|
||||
#qunit-testrunner-toolbar {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
color: #5E740B;
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar .clearfix {
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar label {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar input[type=checkbox],
|
||||
#qunit-testrunner-toolbar input[type=radio] {
|
||||
margin: 3px;
|
||||
vertical-align: -2px;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar input[type=text] {
|
||||
box-sizing: border-box;
|
||||
height: 1.6em;
|
||||
}
|
||||
|
||||
.qunit-url-config,
|
||||
.qunit-filter,
|
||||
#qunit-modulefilter {
|
||||
display: inline-block;
|
||||
line-height: 2.1em;
|
||||
}
|
||||
|
||||
.qunit-filter,
|
||||
#qunit-modulefilter {
|
||||
float: right;
|
||||
position: relative;
|
||||
margin-left: 1em;
|
||||
}
|
||||
|
||||
.qunit-url-config label {
|
||||
margin-right: 0.5em;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-search {
|
||||
box-sizing: border-box;
|
||||
width: 400px;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-search-container:after {
|
||||
position: absolute;
|
||||
right: 0.3em;
|
||||
content: "\25bc";
|
||||
color: black;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown {
|
||||
/* align with #qunit-modulefilter-search */
|
||||
box-sizing: border-box;
|
||||
width: 400px;
|
||||
position: absolute;
|
||||
right: 0;
|
||||
top: 50%;
|
||||
margin-top: 0.8em;
|
||||
|
||||
border: 1px solid #D3D3D3;
|
||||
border-top: none;
|
||||
border-radius: 0 0 .25em .25em;
|
||||
color: #000;
|
||||
background-color: #F5F5F5;
|
||||
z-index: 99;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown a {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown .clickable.checked {
|
||||
font-weight: bold;
|
||||
color: #000;
|
||||
background-color: #D2E0E6;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown .clickable:hover {
|
||||
color: #FFF;
|
||||
background-color: #0D3349;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-actions {
|
||||
display: block;
|
||||
overflow: auto;
|
||||
|
||||
/* align with #qunit-modulefilter-dropdown-list */
|
||||
font: smaller/1.5em sans-serif;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > * {
|
||||
box-sizing: border-box;
|
||||
max-height: 2.8em;
|
||||
display: block;
|
||||
padding: 0.4em;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > button {
|
||||
float: right;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown #qunit-modulefilter-actions > :last-child {
|
||||
/* insert padding to align with checkbox margins */
|
||||
padding-left: 3px;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown-list {
|
||||
max-height: 200px;
|
||||
overflow-y: auto;
|
||||
margin: 0;
|
||||
border-top: 2px groove threedhighlight;
|
||||
padding: 0.4em 0 0;
|
||||
font: smaller/1.5em sans-serif;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown-list li {
|
||||
white-space: nowrap;
|
||||
overflow: hidden;
|
||||
text-overflow: ellipsis;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-dropdown-list .clickable {
|
||||
display: block;
|
||||
padding-left: 0.15em;
|
||||
}
|
||||
|
||||
|
||||
/** Tests: Pass/Fail */
|
||||
|
||||
#qunit-tests {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li {
|
||||
padding: 0.4em 1em 0.4em 1em;
|
||||
border-bottom: 1px solid #FFF;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests > li {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests li.running,
|
||||
#qunit-tests li.pass,
|
||||
#qunit-tests li.fail,
|
||||
#qunit-tests li.skipped,
|
||||
#qunit-tests li.aborted {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass {
|
||||
position: relative;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass li.running,
|
||||
#qunit-tests.hidepass li.pass:not(.todo) {
|
||||
visibility: hidden;
|
||||
position: absolute;
|
||||
width: 0;
|
||||
height: 0;
|
||||
padding: 0;
|
||||
border: 0;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
#qunit-tests li strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#qunit-tests li.skipped strong {
|
||||
cursor: default;
|
||||
}
|
||||
|
||||
#qunit-tests li a {
|
||||
padding: 0.5em;
|
||||
color: #C2CCD1;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests li p a {
|
||||
padding: 0.25em;
|
||||
color: #6B6464;
|
||||
}
|
||||
#qunit-tests li a:hover,
|
||||
#qunit-tests li a:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#qunit-tests li .runtime {
|
||||
float: right;
|
||||
font-size: smaller;
|
||||
}
|
||||
|
||||
.qunit-assert-list {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #FFF;
|
||||
|
||||
border-radius: 5px;
|
||||
}
|
||||
|
||||
.qunit-source {
|
||||
margin: 0.6em 0 0.3em;
|
||||
}
|
||||
|
||||
.qunit-collapsed {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests table {
|
||||
border-collapse: collapse;
|
||||
margin-top: 0.2em;
|
||||
}
|
||||
|
||||
#qunit-tests th {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
padding: 0 0.5em 0 0;
|
||||
}
|
||||
|
||||
#qunit-tests td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#qunit-tests pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#qunit-tests del {
|
||||
color: #374E0C;
|
||||
background-color: #E0F2BE;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests ins {
|
||||
color: #500;
|
||||
background-color: #FFCACA;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** Test Counts */
|
||||
|
||||
#qunit-tests b.counts { color: #000; }
|
||||
#qunit-tests b.passed { color: #5E740B; }
|
||||
#qunit-tests b.failed { color: #710909; }
|
||||
|
||||
#qunit-tests li li {
|
||||
padding: 5px;
|
||||
background-color: #FFF;
|
||||
border-bottom: none;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/*** Passing Styles */
|
||||
|
||||
#qunit-tests li li.pass {
|
||||
color: #3C510C;
|
||||
background-color: #FFF;
|
||||
border-left: 10px solid #C6E746;
|
||||
}
|
||||
|
||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||
#qunit-tests .pass .test-name { color: #366097; }
|
||||
|
||||
#qunit-tests .pass .test-actual,
|
||||
#qunit-tests .pass .test-expected { color: #999; }
|
||||
|
||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||
|
||||
/*** Failing Styles */
|
||||
|
||||
#qunit-tests li li.fail {
|
||||
color: #710909;
|
||||
background-color: #FFF;
|
||||
border-left: 10px solid #EE5757;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#qunit-tests > li:last-child {
|
||||
border-radius: 0 0 5px 5px;
|
||||
}
|
||||
|
||||
#qunit-tests .fail { color: #000; background-color: #EE5757; }
|
||||
#qunit-tests .fail .test-name,
|
||||
#qunit-tests .fail .module-name { color: #000; }
|
||||
|
||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
||||
#qunit-tests .fail .test-expected { color: #008000; }
|
||||
|
||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
||||
|
||||
|
||||
/*** Aborted tests */
|
||||
#qunit-tests .aborted { color: #000; background-color: orange; }
|
||||
/*** Skipped tests */
|
||||
|
||||
#qunit-tests .skipped {
|
||||
background-color: #EBECE9;
|
||||
}
|
||||
|
||||
#qunit-tests .qunit-todo-label,
|
||||
#qunit-tests .qunit-skipped-label {
|
||||
background-color: #F4FF77;
|
||||
display: inline-block;
|
||||
font-style: normal;
|
||||
color: #366097;
|
||||
line-height: 1.8em;
|
||||
padding: 0 0.5em;
|
||||
margin: -0.4em 0.4em -0.4em 0;
|
||||
}
|
||||
|
||||
#qunit-tests .qunit-todo-label {
|
||||
background-color: #EEE;
|
||||
}
|
||||
|
||||
/** Result */
|
||||
|
||||
#qunit-testresult {
|
||||
color: #2B81AF;
|
||||
background-color: #D2E0E6;
|
||||
|
||||
border-bottom: 1px solid #FFF;
|
||||
}
|
||||
#qunit-testresult .clearfix {
|
||||
height: 0;
|
||||
clear: both;
|
||||
}
|
||||
#qunit-testresult .module-name {
|
||||
font-weight: 700;
|
||||
}
|
||||
#qunit-testresult-display {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
width: 85%;
|
||||
float:left;
|
||||
}
|
||||
#qunit-testresult-controls {
|
||||
padding: 0.5em 1em 0.5em 1em;
|
||||
width: 10%;
|
||||
float:left;
|
||||
}
|
||||
|
||||
/** Fixture */
|
||||
|
||||
#qunit-fixture {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
}
|
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,235 @@
|
|||
/**
|
||||
* QUnit v1.10.0 - A JavaScript Unit Testing Framework
|
||||
*
|
||||
* http://qunitjs.com
|
||||
*
|
||||
* Copyright 2012 jQuery Foundation and other contributors
|
||||
* Released under the MIT license.
|
||||
* http://jquery.org/license
|
||||
*/
|
||||
|
||||
/** Font Family and Sizes */
|
||||
|
||||
#qunit-tests, #qunit-header, #qunit-banner, #qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult {
|
||||
font-family: "Helvetica Neue Light", "HelveticaNeue-Light", "Helvetica Neue", Calibri, Helvetica, Arial, sans-serif;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar, #qunit-userAgent, #qunit-testresult, #qunit-tests li { font-size: small; }
|
||||
#qunit-tests { font-size: smaller; }
|
||||
|
||||
|
||||
/** Resets */
|
||||
|
||||
#qunit-tests, #qunit-tests ol, #qunit-header, #qunit-banner, #qunit-userAgent, #qunit-testresult, #qunit-modulefilter {
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
|
||||
/** Header */
|
||||
|
||||
#qunit-header {
|
||||
padding: 0.5em 0 0.5em 1em;
|
||||
|
||||
color: #8699a4;
|
||||
background-color: #0d3349;
|
||||
|
||||
font-size: 1.5em;
|
||||
line-height: 1em;
|
||||
font-weight: normal;
|
||||
|
||||
border-radius: 5px 5px 0 0;
|
||||
-moz-border-radius: 5px 5px 0 0;
|
||||
-webkit-border-top-right-radius: 5px;
|
||||
-webkit-border-top-left-radius: 5px;
|
||||
}
|
||||
|
||||
#qunit-header a {
|
||||
text-decoration: none;
|
||||
color: #c2ccd1;
|
||||
}
|
||||
|
||||
#qunit-header a:hover,
|
||||
#qunit-header a:focus {
|
||||
color: #fff;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar label {
|
||||
display: inline-block;
|
||||
padding: 0 .5em 0 .1em;
|
||||
}
|
||||
|
||||
#qunit-banner {
|
||||
height: 5px;
|
||||
}
|
||||
|
||||
#qunit-testrunner-toolbar {
|
||||
padding: 0.5em 0 0.5em 2em;
|
||||
color: #5E740B;
|
||||
background-color: #eee;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#qunit-userAgent {
|
||||
padding: 0.5em 0 0.5em 2.5em;
|
||||
background-color: #2b81af;
|
||||
color: #fff;
|
||||
text-shadow: rgba(0, 0, 0, 0.5) 2px 2px 1px;
|
||||
}
|
||||
|
||||
#qunit-modulefilter-container {
|
||||
float: right;
|
||||
}
|
||||
|
||||
/** Tests: Pass/Fail */
|
||||
|
||||
#qunit-tests {
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests li {
|
||||
padding: 0.4em 0.5em 0.4em 2.5em;
|
||||
border-bottom: 1px solid #fff;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
#qunit-tests.hidepass li.pass, #qunit-tests.hidepass li.running {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#qunit-tests li strong {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#qunit-tests li a {
|
||||
padding: 0.5em;
|
||||
color: #c2ccd1;
|
||||
text-decoration: none;
|
||||
}
|
||||
#qunit-tests li a:hover,
|
||||
#qunit-tests li a:focus {
|
||||
color: #000;
|
||||
}
|
||||
|
||||
#qunit-tests ol {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.5em;
|
||||
|
||||
background-color: #fff;
|
||||
|
||||
border-radius: 5px;
|
||||
-moz-border-radius: 5px;
|
||||
-webkit-border-radius: 5px;
|
||||
}
|
||||
|
||||
#qunit-tests table {
|
||||
border-collapse: collapse;
|
||||
margin-top: .2em;
|
||||
}
|
||||
|
||||
#qunit-tests th {
|
||||
text-align: right;
|
||||
vertical-align: top;
|
||||
padding: 0 .5em 0 0;
|
||||
}
|
||||
|
||||
#qunit-tests td {
|
||||
vertical-align: top;
|
||||
}
|
||||
|
||||
#qunit-tests pre {
|
||||
margin: 0;
|
||||
white-space: pre-wrap;
|
||||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
#qunit-tests del {
|
||||
background-color: #e0f2be;
|
||||
color: #374e0c;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
#qunit-tests ins {
|
||||
background-color: #ffcaca;
|
||||
color: #500;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
/*** Test Counts */
|
||||
|
||||
#qunit-tests b.counts { color: black; }
|
||||
#qunit-tests b.passed { color: #5E740B; }
|
||||
#qunit-tests b.failed { color: #710909; }
|
||||
|
||||
#qunit-tests li li {
|
||||
padding: 5px;
|
||||
background-color: #fff;
|
||||
border-bottom: none;
|
||||
list-style-position: inside;
|
||||
}
|
||||
|
||||
/*** Passing Styles */
|
||||
|
||||
#qunit-tests li li.pass {
|
||||
color: #3c510c;
|
||||
background-color: #fff;
|
||||
border-left: 10px solid #C6E746;
|
||||
}
|
||||
|
||||
#qunit-tests .pass { color: #528CE0; background-color: #D2E0E6; }
|
||||
#qunit-tests .pass .test-name { color: #366097; }
|
||||
|
||||
#qunit-tests .pass .test-actual,
|
||||
#qunit-tests .pass .test-expected { color: #999999; }
|
||||
|
||||
#qunit-banner.qunit-pass { background-color: #C6E746; }
|
||||
|
||||
/*** Failing Styles */
|
||||
|
||||
#qunit-tests li li.fail {
|
||||
color: #710909;
|
||||
background-color: #fff;
|
||||
border-left: 10px solid #EE5757;
|
||||
white-space: pre;
|
||||
}
|
||||
|
||||
#qunit-tests > li:last-child {
|
||||
border-radius: 0 0 5px 5px;
|
||||
-moz-border-radius: 0 0 5px 5px;
|
||||
-webkit-border-bottom-right-radius: 5px;
|
||||
-webkit-border-bottom-left-radius: 5px;
|
||||
}
|
||||
|
||||
#qunit-tests .fail { color: #000000; background-color: #EE5757; }
|
||||
#qunit-tests .fail .test-name,
|
||||
#qunit-tests .fail .module-name { color: #000000; }
|
||||
|
||||
#qunit-tests .fail .test-actual { color: #EE5757; }
|
||||
#qunit-tests .fail .test-expected { color: green; }
|
||||
|
||||
#qunit-banner.qunit-fail { background-color: #EE5757; }
|
||||
|
||||
|
||||
/** Result */
|
||||
|
||||
#qunit-testresult {
|
||||
padding: 0.5em 0.5em 0.5em 2.5em;
|
||||
|
||||
color: #2b81af;
|
||||
background-color: #D2E0E6;
|
||||
|
||||
border-bottom: 1px solid white;
|
||||
}
|
||||
#qunit-testresult .module-name {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
/** Fixture */
|
||||
|
||||
#qunit-fixture {
|
||||
position: absolute;
|
||||
top: -10000px;
|
||||
left: -10000px;
|
||||
width: 1000px;
|
||||
height: 1000px;
|
||||
}
|
|
@ -0,0 +1,259 @@
|
|||
// vim: set ts=2 sw=2 et tw=80:
|
||||
class App {
|
||||
static get BRUSHES() {
|
||||
return {
|
||||
"PenBrush": new PenBrush(),
|
||||
"DiscBrush": new DiscBrush(),
|
||||
"StarBrush": new StarBrush(),
|
||||
};
|
||||
}
|
||||
|
||||
constructor(conf) {
|
||||
if (!(typeof conf === 'object' && conf)) {
|
||||
throw new Error('Argument conf different from specification');
|
||||
}
|
||||
|
||||
this.canvas = document.getElementById(conf.canvas);
|
||||
if (!this.canvas || this.canvas.tagName !== 'CANVAS') {
|
||||
throw new Error(`canvas is not a canvas`);
|
||||
}
|
||||
|
||||
this.ctx = this.canvas.getContext('2d');
|
||||
this.favourites = document.querySelector('div#favourites');
|
||||
|
||||
if (typeof conf.buttons === 'object' && conf.buttons) {
|
||||
this.buttons = {}
|
||||
for (const b of ['clear', 'undo', 'camera'])
|
||||
this.buttons[b] = document.getElementById(conf.buttons[b]);
|
||||
|
||||
if (this.buttons.clear) {
|
||||
this.buttons.clear.addEventListener('click', () => {
|
||||
this.erase();
|
||||
this.background = null;
|
||||
history.clear();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.buttons.undo) {
|
||||
this.buttons.undo.addEventListener('click', () => {
|
||||
history.pop();
|
||||
this.redrawAll();
|
||||
});
|
||||
}
|
||||
|
||||
if (this.buttons.camera) {
|
||||
this.buttons.camera.addEventListener('click', () => {
|
||||
const base64 = this.canvas.toDataURL();
|
||||
const img = document.createElement('img');
|
||||
img.src = base64;
|
||||
|
||||
const form = document.createElement('form');
|
||||
form.method = 'POST';
|
||||
form.action = '/favorites';
|
||||
|
||||
const image = document.createElement('input');
|
||||
image.type = 'hidden';
|
||||
image.name = 'dataURL';
|
||||
image.value = base64;
|
||||
form.appendChild(image);
|
||||
|
||||
const lblName = document.createElement('label');
|
||||
lblName.setAttribute('for', 'name');
|
||||
lblName.innerText = 'Name:';
|
||||
form.appendChild(lblName);
|
||||
|
||||
const name = document.createElement('input');
|
||||
name.type = 'text';
|
||||
name.name = 'name';
|
||||
name.placeholder = 'Name';
|
||||
name.value = 'New Name';
|
||||
form.appendChild(name);
|
||||
|
||||
const submit = document.createElement('button');
|
||||
submit.innerText = 'Save';
|
||||
form.appendChild(submit);
|
||||
|
||||
this.favourites.appendChild(img);
|
||||
this.favourites.appendChild(form);
|
||||
});
|
||||
}
|
||||
|
||||
const player = document.createElement('video');
|
||||
player.addEventListener('loadeddata', () => {
|
||||
player.play();
|
||||
setTimeout(() => {
|
||||
const imgCanvas = document.createElement('canvas');
|
||||
imgCanvas.width = this.canvas.width;
|
||||
imgCanvas.height = this.canvas.height;
|
||||
const imgCtx = imgCanvas.getContext('2d');
|
||||
imgCtx.drawImage(player, 0, 0, canvas.width, canvas.height);
|
||||
const imgData = imgCtx.getImageData(0, 0, imgCanvas.width,
|
||||
imgCanvas.height);
|
||||
|
||||
for (let i = 0; i < data.length; i += 4) {
|
||||
const pixel = (data[i] + 2 * data[i+1] + data[i+2]) / 4;
|
||||
data[i] = data[i+1] = data[i+2] = pixel;
|
||||
}
|
||||
imgCtx.putImageData(imgData, 0, 0);
|
||||
|
||||
const img = document.createElement('img');
|
||||
img.src = imgCanvas.toDataURL();
|
||||
img.addEventListener('load', () => {
|
||||
this.background = img;
|
||||
this.redrawAll();
|
||||
});
|
||||
|
||||
player.srcObject.getVideoTracks().forEach(track => track.stop());
|
||||
}, 100);
|
||||
});
|
||||
|
||||
const button = document.createElement('button');
|
||||
button.type = 'button';
|
||||
button.innerHTML = 'Photo';
|
||||
button.addEventListener('click', () => {
|
||||
navigator.mediaDevices.getUserMedia({ video: true })
|
||||
.then((stream) => {
|
||||
player.srcObject = stream;
|
||||
});
|
||||
});
|
||||
|
||||
document.getElementById('left-toolbar').appendChild(button);
|
||||
}
|
||||
|
||||
this.ctx.lineWidth = 1;
|
||||
this.strokeStyle = this.constructor.defaultStrokeStyle;
|
||||
this.brush = "PenBrush";
|
||||
|
||||
const brushToolbar = document.querySelector('#brush-toolbar');
|
||||
if (brushToolbar) {
|
||||
for (const name in App.BRUSHES) {
|
||||
const b = document.createElement('button');
|
||||
b.innerText = name;
|
||||
b.addEventListener('click', () => this.brush = name);
|
||||
brushToolbar.appendChild(b);
|
||||
}
|
||||
|
||||
const label = document.createElement('label');
|
||||
label.setAttribute('for', 'color');
|
||||
const color = document.createElement('input');
|
||||
color.type = 'color';
|
||||
color.name = 'color';
|
||||
color.value = this.constructor.defaultStrokeStyle;
|
||||
color.addEventListener('change', () => this.strokeStyle = color.value);
|
||||
brushToolbar.appendChild(label);
|
||||
brushToolbar.appendChild(color);
|
||||
}
|
||||
|
||||
const toMouse = (e, func) => {
|
||||
if (e && e.touches && e.touches[0]) {
|
||||
return func.bind(this)({
|
||||
offsetX: e.touches[0].pageX - this.canvas.offsetLeft,
|
||||
offsetY: e.touches[0].pageY - this.canvas.offsetTop
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
this.canvas.addEventListener('touchstart', e => toMouse(e, this.startPath));
|
||||
this.canvas.addEventListener('mousedown', this.startPath.bind(this));
|
||||
this.canvas.addEventListener('touchmove', e => toMouse(e, this.draw));
|
||||
this.canvas.addEventListener('mousemove', this.draw.bind(this));
|
||||
this.canvas.addEventListener('touchcancel', e => toMouse(e, this.endPath));
|
||||
this.canvas.addEventListener('mouseup', this.endPath.bind(this));
|
||||
this.canvas.addEventListener('mouseout', this.endPath.bind(this));
|
||||
}
|
||||
|
||||
static get defaultStrokeStyle() {
|
||||
return 'black';
|
||||
}
|
||||
|
||||
get strokeStyle() {
|
||||
if (this.ctx.strokeStyle == '#000000') {
|
||||
return 'black';
|
||||
}
|
||||
return this.ctx.strokeStyle;
|
||||
}
|
||||
|
||||
set strokeStyle(style) {
|
||||
if (typeof style !== 'string') {
|
||||
throw new Error('style is not a string');
|
||||
}
|
||||
this.ctx.strokeStyle = style;
|
||||
}
|
||||
|
||||
get brush() {
|
||||
return this._brush.name;
|
||||
}
|
||||
|
||||
set brush(brushName) {
|
||||
this._brush = App.BRUSHES[brushName];
|
||||
}
|
||||
|
||||
erase() {
|
||||
this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
|
||||
}
|
||||
|
||||
startPath(e, record = true) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(e.offsetX, e.offsetY);
|
||||
|
||||
if (record) {
|
||||
history.initializeNewPath();
|
||||
history.push(new Stroke(this.brush, this.strokeStyle,
|
||||
e.offsetX, e.offsetY));
|
||||
}
|
||||
|
||||
if (e instanceof MouseEvent) {
|
||||
this.mousedown = true;
|
||||
}
|
||||
}
|
||||
|
||||
draw(e, beginNew = true, record = true) {
|
||||
if (this.mousedown || !(e instanceof MouseEvent)) {
|
||||
this._brush.draw(this.ctx, this.strokeStyle, e.offsetX, e.offsetY);
|
||||
|
||||
if (record) {
|
||||
history.push(new Stroke(this.brush, this.strokeStyle,
|
||||
e.offsetX, e.offsetY));
|
||||
}
|
||||
|
||||
if (beginNew) {
|
||||
this.ctx.beginPath();
|
||||
this.ctx.moveTo(e.offsetX, e.offsetY);
|
||||
} else if (e instanceof MouseEvent) {
|
||||
this.mousedown = false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
endPath(e, record = true) {
|
||||
this.draw(e, false, record);
|
||||
}
|
||||
|
||||
drawPath(path) {
|
||||
const last = path.length - 1;
|
||||
const lastBrush = this.brush;
|
||||
const lastStyle = this.strokeStyle;
|
||||
for (let i = 0; i <= last; i++) {
|
||||
this.brush = path[i].brushName;
|
||||
this.strokeStyle = path[i].strokeStyle;
|
||||
switch(i) {
|
||||
case 0: this.startPath(path[i], false); break;
|
||||
case last: this.endPath(path[i], false); break;
|
||||
default: this.draw(path[i], true, false);
|
||||
}
|
||||
}
|
||||
this.brush = lastBrush;
|
||||
this.strokeStyle = lastStyle;
|
||||
}
|
||||
|
||||
redrawAll() {
|
||||
this.erase();
|
||||
if (this.background) {
|
||||
this.ctx.drawImage(this.background, 0, 0, this.canvas.width,
|
||||
this.canvas.height);
|
||||
}
|
||||
for (const path of history.paths) {
|
||||
this.drawPath(path);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,53 @@
|
|||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
class PenBrush {
|
||||
constructor() {
|
||||
this.opacity = 1;
|
||||
this.name = "PenBrush";
|
||||
}
|
||||
|
||||
draw(ctx, strokeStyle, x, y) {
|
||||
ctx.lineJoin = ctx.lineCap = 'round';
|
||||
ctx.strokeStyle = strokeStyle;
|
||||
ctx.lineTo(x, y);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
class DiscBrush extends PenBrush {
|
||||
static get RADIUS() { return 10; }
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "DiscBrush";
|
||||
}
|
||||
|
||||
draw(ctx, strokeStyle, x, y) {
|
||||
ctx.beginPath(); // clear previous path starting
|
||||
ctx.ellipse(x, y, 10, 10, 0, 0, 2 * Math.PI);
|
||||
ctx.fill();
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
||||
|
||||
class StarBrush extends PenBrush {
|
||||
constructor() {
|
||||
super();
|
||||
this.name = "StarBrush";
|
||||
}
|
||||
|
||||
draw(ctx, strokeStyle, x, y) {
|
||||
ctx.moveTo(x - 8, y - 3);
|
||||
ctx.lineTo(x - 3, y - 3);
|
||||
ctx.lineTo(x, y - 8);
|
||||
ctx.lineTo(x + 3, y - 3);
|
||||
ctx.lineTo(x + 8, y - 3);
|
||||
ctx.lineTo(x + 4, y + 1);
|
||||
ctx.lineTo(x + 4, y + 6);
|
||||
ctx.lineTo(x, y + 3);
|
||||
ctx.lineTo(x - 4, y + 6);
|
||||
ctx.lineTo(x - 4, y + 1);
|
||||
ctx.lineTo(x - 8, y - 3);
|
||||
ctx.stroke();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,37 @@
|
|||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const history = {
|
||||
paths: []
|
||||
}
|
||||
|
||||
history.pop = () => {
|
||||
if (history.paths.length == 0) return;
|
||||
return history.paths.pop();
|
||||
};
|
||||
|
||||
history.initializeNewPath = () => {
|
||||
history.paths.push([]);
|
||||
};
|
||||
|
||||
history.push = (stroke) => {
|
||||
if (!stroke || !stroke instanceof Stroke) {
|
||||
throw new Error(JSON.stringify(stroke) + ' is not a Stroke instance');
|
||||
}
|
||||
history.paths[history.paths.length - 1].push(stroke);
|
||||
return history.paths[history.paths.length - 1];
|
||||
}
|
||||
|
||||
history.clear = () => {
|
||||
history.paths = [];
|
||||
};
|
||||
|
||||
|
||||
|
||||
class Stroke {
|
||||
constructor(brushName, strokeStyle, x, y) {
|
||||
this.brushName = brushName;
|
||||
this.strokeStyle = strokeStyle;
|
||||
this.offsetX = x;
|
||||
this.offsetY = y;
|
||||
}
|
||||
}
|
|
@ -0,0 +1,133 @@
|
|||
/* vim: set ts=2 sw=2 et tw=80: */
|
||||
|
||||
body {
|
||||
font-family: "Lucida Sans Unicode", "Lucida Grande", sans-serif;
|
||||
}
|
||||
|
||||
#app {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#canvas {
|
||||
border: 1px solid #bbb;
|
||||
}
|
||||
|
||||
#left-toolbar, #brush-toolbar {
|
||||
width: 6rem;
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
|
||||
.toolbar button, .toolbar input {
|
||||
width: 100%;
|
||||
padding: .5rem 0;
|
||||
margin-bottom: 10px;
|
||||
min-height: 3rem;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
#brush-toolbar {
|
||||
margin: 0 .5rem;
|
||||
}
|
||||
|
||||
#palette {
|
||||
display: flex;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-around;
|
||||
width: 53px;
|
||||
height: 204px;
|
||||
background-color: buttonface;
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.toolbar #camera-btn {
|
||||
font-size: 1.5em;
|
||||
line-height: 1.5em;
|
||||
color: #444;
|
||||
background: #eee;
|
||||
border: none;
|
||||
border-radius: 3px;
|
||||
height: 40px;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.toolbar #camera-btn:hover {
|
||||
background: #70a0e8;
|
||||
}
|
||||
|
||||
.toolbar #camera-btn:active {
|
||||
background: #0e57c3;
|
||||
}
|
||||
|
||||
.p-color {
|
||||
width: 25px;
|
||||
height: 25px;
|
||||
}
|
||||
|
||||
.black {background-color:rgb(0, 0, 0);}
|
||||
.dark-gray {background-color:rgb(87, 87, 87);}
|
||||
.red {background-color:rgb(173, 35, 35);}
|
||||
.blue {background-color:rgb(42, 75, 215);}
|
||||
.green {background-color:rgb(29, 105, 20);}
|
||||
.brown {background-color:rgb(129, 74, 25);}
|
||||
.purple {background-color:rgb(129, 38, 192);}
|
||||
.light-gray {background-color:rgb(160, 160, 160);}
|
||||
.light-green {background-color:rgb(129, 197, 122);}
|
||||
.light-blue {background-color:rgb(157, 175, 255);}
|
||||
.cyan {background-color:rgb(41, 208, 208);}
|
||||
.orange {background-color:rgb(255, 146, 51);}
|
||||
.yellow {background-color:rgb(255, 238, 51);}
|
||||
.tan {background-color:rgb(233, 222, 187);}
|
||||
.pink {background-color:rgb(255, 205, 243);}
|
||||
.white {background-color:rgb(255, 255, 255);}
|
||||
|
||||
#clock {
|
||||
background-color: #ddd;
|
||||
width: 360px;
|
||||
height: 48px;
|
||||
text-align: center;
|
||||
position: relative;
|
||||
font-size: 2.5rem;
|
||||
margin-top: -10px;
|
||||
font-weight: bold;
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
#clock-time,
|
||||
#progress-bar {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
top: 0;
|
||||
left: 0;
|
||||
}
|
||||
#clock-time {
|
||||
z-index: 10;
|
||||
line-height: 48px; /* same height as #clock so that the time is vertically centered */
|
||||
}
|
||||
|
||||
#progress-bar {
|
||||
width: 0%;
|
||||
height: 48px;
|
||||
background-color: #8DFF80;
|
||||
text-align: center;
|
||||
line-height: 30px;
|
||||
color: white;
|
||||
}
|
||||
|
||||
#favourites {
|
||||
margin: 5px;
|
||||
float: left;
|
||||
width: 200px;
|
||||
}
|
||||
|
||||
#favourites img {
|
||||
width: 90%;
|
||||
height: 100px;
|
||||
box-shadow: 1px 1px 3px 1px rgba(0, 0, 0, 0.42);
|
||||
margin-left: 10px;
|
||||
}
|
||||
|
||||
#favourites div.desc {
|
||||
padding: 15px;
|
||||
text-align: center;
|
||||
}
|
|
@ -0,0 +1,304 @@
|
|||
<!doctype html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="initial-scale=1.0">
|
||||
<title>Assignment 7 - Tests for Tasks 1 and 2</title>
|
||||
<link rel="stylesheet" href="qunit/qunit-2.9.2.css">
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<script src="js/fetch.js"></script>
|
||||
<script src="qunit/qunit-2.9.2.js"></script>
|
||||
<div id="qunit"></div>
|
||||
<div id="qunit-fixture"></div>
|
||||
<script>
|
||||
|
||||
//some test payloads
|
||||
const headers = {};
|
||||
const baseURL = "/test/fetch";
|
||||
const jsonURL = baseURL + "/json/ok";
|
||||
const body = "test body as a string"
|
||||
const jsonBody_req = {
|
||||
json: body,
|
||||
rnd: Math.random()
|
||||
};
|
||||
const jsonBody_resp = (text) => {
|
||||
return {
|
||||
text, body: jsonBody_req
|
||||
}
|
||||
}
|
||||
|
||||
var notValidJSON = () => {};
|
||||
|
||||
/*
|
||||
* Make sure the fetch_test/router.js is installed under /test/fetch and the server is running for these tests to work
|
||||
*
|
||||
*/
|
||||
|
||||
function testOk(f,method,url,headers,body) {
|
||||
let msg = f.name+"('"+method+"','"+url+"','"+JSON.stringify(headers)+","+JSON.stringify(body)+") should accept the input";
|
||||
|
||||
return async function(assert) {
|
||||
try {
|
||||
await f(method,url,headers,body);
|
||||
result = true;
|
||||
} catch (error) {
|
||||
console.log(msg, error);
|
||||
msg += "\n but \n" + error;
|
||||
result = false;
|
||||
}
|
||||
assert.ok(result, msg);
|
||||
}
|
||||
}
|
||||
|
||||
function testFail(f,method,url,headers,body) {
|
||||
return async function(assert) {
|
||||
try{
|
||||
await f(method,url,headers,body);
|
||||
result = false;
|
||||
}
|
||||
catch(error) {
|
||||
result = true;
|
||||
}
|
||||
assert.ok(result, f.name+"("+JSON.stringify(method)+","+JSON.stringify(url)+","+JSON.stringify(headers)+","+JSON.stringify(body)+") should reject the input");
|
||||
}
|
||||
}
|
||||
|
||||
function testStatusBodyHeaders(f,method,url,headers,body,status,text,resp_headers) {
|
||||
return async function(assert) {
|
||||
return f(method,url,headers,body)
|
||||
.then((res)=>{
|
||||
assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
|
||||
Object.keys(resp_headers).forEach((k)=>{
|
||||
assert.ok(res.headers.has(k), method+" "+url+" response includes header "+k);
|
||||
assert.equal(res.headers.get(k), resp_headers[k], method+" "+url+" response header "+k+" has expected value "+JSON.stringify(resp_headers[k]));
|
||||
});
|
||||
return res.text();
|
||||
}).then((resp)=>{
|
||||
assert.equal(resp, text, method+" "+url+" returns expected response body "+JSON.stringify(text));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testStatusBody(f,method,url,headers,body,status,text) {
|
||||
return async function(assert) {
|
||||
return f(method,url,headers,body)
|
||||
.then((res)=>{
|
||||
assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
|
||||
return res.text();
|
||||
}).then((resp)=>{
|
||||
assert.equal(resp, text, method+" "+url+" returns expected response body "+JSON.stringify(text));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testStatusJSONBody(f,method,url,headers,body,status,json) {
|
||||
return async function(assert) {
|
||||
return f(method,url,headers,body)
|
||||
.then((res)=>{
|
||||
//assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
|
||||
assert.deepEqual(res, json, method+" "+url+" returns expected response body "+JSON.stringify(json));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testStatus(f,method,url,headers,body,status,text) {
|
||||
return async function(assert) {
|
||||
return f(method,url,headers,body)
|
||||
.then((res)=>{
|
||||
assert.equal(res.status, status, method+" "+url+" should return "+status+" status code");
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testRedirect(f,method,url,headers,body,text) {
|
||||
return async function(assert) {
|
||||
return f(method,url,headers,body)
|
||||
.then((res)=>{
|
||||
assert.ok(res.redirected, method+" "+url+" should have been redirected");
|
||||
return res.text();
|
||||
}).then((resp)=>{
|
||||
assert.equal(resp, text, method+" "+url+" has expected response body after redirection "+JSON.stringify(text));
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
function testAll(a){
|
||||
return function(assert) {
|
||||
let b = [];
|
||||
a.forEach((f)=>{b.push(f(assert))});
|
||||
return Promise.all(b);
|
||||
}
|
||||
}
|
||||
|
||||
QUnit.module("Task 1: doFetchRequest Request Validation", {});
|
||||
|
||||
QUnit.test("reject incorrect methods", testAll(
|
||||
[
|
||||
testFail(doFetchRequest,"WRONG", baseURL,headers),
|
||||
testFail(doFetchRequest,"RANDOM", baseURL,headers),
|
||||
testFail(doFetchRequest,"",baseURL,headers),
|
||||
testFail(doFetchRequest,undefined,baseURL,headers),
|
||||
testFail(doFetchRequest,404,baseURL,headers)
|
||||
]));
|
||||
|
||||
QUnit.test("GET, HEAD, OPTIONS, DELETE should not have a body", testAll(
|
||||
[
|
||||
testFail(doFetchRequest,"GET", baseURL,headers,body),
|
||||
testFail(doFetchRequest,"HEAD", baseURL,headers,body),
|
||||
testFail(doFetchRequest,"OPTIONS",baseURL,headers,body),
|
||||
testFail(doFetchRequest,"DELETE",baseURL,headers,body),
|
||||
testOk(doFetchRequest,"GET", baseURL,headers),
|
||||
testOk(doFetchRequest,"HEAD", baseURL,headers),
|
||||
testOk(doFetchRequest,"OPTIONS",baseURL,headers),
|
||||
testOk(doFetchRequest,"DELETE",baseURL,headers)
|
||||
]));
|
||||
|
||||
QUnit.test("POST, PUT, PATCH require the body", testAll(
|
||||
[
|
||||
testFail(doFetchRequest,"POST", baseURL,headers),
|
||||
testFail(doFetchRequest,"PUT", baseURL,headers),
|
||||
testFail(doFetchRequest,"PATCH",baseURL,headers),
|
||||
testOk(doFetchRequest,"POST", baseURL,headers,body),
|
||||
testOk(doFetchRequest,"PUT", baseURL,headers,body),
|
||||
testOk(doFetchRequest,"PATCH",baseURL,headers,body)
|
||||
]));
|
||||
|
||||
QUnit.test("POST, PUT, PATCH require a String body", testAll(
|
||||
[
|
||||
testFail(doFetchRequest,"POST", baseURL,headers,{}),
|
||||
testFail(doFetchRequest,"PUT", baseURL,headers,{}),
|
||||
testFail(doFetchRequest,"PATCH",baseURL,headers,{}),
|
||||
testFail(doFetchRequest,"POST", baseURL,headers,[]),
|
||||
testFail(doFetchRequest,"PUT", baseURL,headers,[]),
|
||||
testFail(doFetchRequest,"PATCH",baseURL,headers,[]),
|
||||
testFail(doFetchRequest,"POST", baseURL,headers,999),
|
||||
testFail(doFetchRequest,"PUT", baseURL,headers,999),
|
||||
testFail(doFetchRequest,"PATCH",baseURL,headers,999)
|
||||
]));
|
||||
|
||||
QUnit.module("Task 1: doFetchRequest Response Validation", {});
|
||||
|
||||
QUnit.test("GET/DELETE return expected status and body", testAll(
|
||||
[
|
||||
testStatusBody(doFetchRequest,"GET",baseURL,{"Accept": 'text/html'},undefined,200,"GET Working"),
|
||||
testStatus(doFetchRequest,"GET",baseURL+"/not/found",{"Accept": 'text/html'},undefined,404),
|
||||
testStatusBody(doFetchRequest,"GET",baseURL,{"Accept": 'application/json'},undefined,200,"{\"text\":\"GET Working\"}"),
|
||||
testStatusBody(doFetchRequest,"DELETE",baseURL,{"Accept": 'text/html'},undefined,204,"")
|
||||
]));
|
||||
|
||||
QUnit.test("GET follows redirects", testAll(
|
||||
[
|
||||
testRedirect(doFetchRequest,"GET",baseURL+"/redirect",{"Accept": 'text/html'},undefined,"Redirect Works")
|
||||
]));
|
||||
|
||||
QUnit.test("POST returns expected status, body and headers", testAll(
|
||||
[
|
||||
testStatusBodyHeaders(doFetchRequest,"POST",baseURL+"/new",{"Accept": 'text/html'},"X",201,"POST Working\nX",{'Location':'42'}),
|
||||
testStatusBodyHeaders(doFetchRequest,"POST",baseURL+"/new",{"Accept": 'application/json'},"JSON",201,"{\"text\":\"POST Working\",\"body\":\"JSON\"}",{'Location':'24', 'Content-Type': 'application/json; charset=utf-8'})
|
||||
]));
|
||||
|
||||
QUnit.test("PUT returns expected status, body and headers", testAll(
|
||||
[
|
||||
testStatusBody(doFetchRequest,"PUT",baseURL+"/echo",{"Accept": 'text/html'},"X",200,"X"),
|
||||
testStatusBodyHeaders(doFetchRequest,"PUT",baseURL+"/echo",{"Accept": 'application/json'},"JSON",200,"{\"text\":\"PUT Working\",\"body\":\"JSON\"}",{'Content-Type': 'application/json; charset=utf-8'})
|
||||
]));
|
||||
|
||||
QUnit.module("Task 2: doJSONRequest Request Validation", {});
|
||||
|
||||
QUnit.test("reject incorrect methods", testAll(
|
||||
[
|
||||
testFail(doJSONRequest,"WRONG", jsonURL,headers),
|
||||
testFail(doJSONRequest,"RANDOM", jsonURL,headers),
|
||||
testFail(doJSONRequest,"", jsonURL,headers),
|
||||
testFail(doJSONRequest,undefined, jsonURL,headers),
|
||||
testFail(doJSONRequest,404, jsonURL,headers)
|
||||
]));
|
||||
|
||||
QUnit.test("GET, HEAD, OPTIONS, DELETE should not have a body", testAll(
|
||||
[
|
||||
testFail(doJSONRequest,"GET", jsonURL,headers,body),
|
||||
testFail(doJSONRequest,"HEAD", jsonURL,headers,body),
|
||||
testFail(doJSONRequest,"OPTIONS",jsonURL,headers,body),
|
||||
testFail(doJSONRequest,"DELETE", jsonURL,headers,body),
|
||||
testOk(doJSONRequest,"GET", jsonURL,headers),
|
||||
//HEAD will also have an empty response body
|
||||
//testOk(doJSONRequest,"HEAD", jsonURL,headers),
|
||||
testOk(doJSONRequest,"OPTIONS",jsonURL,headers),
|
||||
testOk(doJSONRequest,"DELETE", jsonURL,headers)
|
||||
]));
|
||||
|
||||
QUnit.test("POST, PUT, PATCH should not have a String body", testAll(
|
||||
[
|
||||
testFail(doJSONRequest,"POST", jsonURL,headers,body),
|
||||
testFail(doJSONRequest,"PUT", jsonURL,headers,body),
|
||||
testFail(doJSONRequest,"PATCH",jsonURL,headers,body),
|
||||
testFail(doJSONRequest,"POST", jsonURL,headers,999),
|
||||
testFail(doJSONRequest,"PUT", jsonURL,headers,999),
|
||||
testFail(doJSONRequest,"PATCH",jsonURL,headers,999),
|
||||
testFail(doJSONRequest,"POST", jsonURL,headers,notValidJSON),
|
||||
testFail(doJSONRequest,"PUT", jsonURL,headers,notValidJSON),
|
||||
testFail(doJSONRequest,"PATCH",jsonURL,headers,notValidJSON)
|
||||
|
||||
]));
|
||||
|
||||
QUnit.test("POST, PUT, PATCH require an Object/Array body", testAll(
|
||||
[
|
||||
testOk(doJSONRequest,"POST", jsonURL,headers,{}),
|
||||
testOk(doJSONRequest,"PUT", jsonURL,headers,{}),
|
||||
testOk(doJSONRequest,"PATCH",jsonURL,headers,{}),
|
||||
testOk(doJSONRequest,"POST", jsonURL,headers,[]),
|
||||
testOk(doJSONRequest,"PUT", jsonURL,headers,[]),
|
||||
testOk(doJSONRequest,"PATCH",jsonURL,headers,[])
|
||||
]));
|
||||
|
||||
QUnit.test("ignore correct accept/content-type headers", testAll(
|
||||
[
|
||||
testOk(doJSONRequest,"POST", jsonURL,{'Accept': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"PUT", jsonURL,{'Accept': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"PATCH",jsonURL,{'Accept': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"POST", jsonURL,{'Accept': 'application/json', 'Content-Type': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"PUT", jsonURL,{'Accept': 'application/json', 'Content-Type': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"PATCH", jsonURL,{'Accept': 'application/json', 'Content-Type': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"PUT", jsonURL,{'Accept': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"PATCH",jsonURL,{'Accept': 'application/json'},{}),
|
||||
testOk(doJSONRequest,"GET", jsonURL,{'Accept': 'application/json'}),
|
||||
testOk(doJSONRequest,"OPTIONS", jsonURL,{'Accept': 'application/json'}),
|
||||
testOk(doJSONRequest,"DELETE",jsonURL,{'Accept': 'application/json'})
|
||||
]));
|
||||
|
||||
QUnit.test("reject incorrect accept/content-type headers", testAll(
|
||||
[
|
||||
testFail(doJSONRequest,"POST", jsonURL,{'Accept': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"PUT", jsonURL,{'Accept': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"PATCH",jsonURL,{'Accept': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"POST", jsonURL,{'Accept': 'text/plain', 'Content-Type': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"PUT", jsonURL,{'Accept': 'text/plain', 'Content-Type': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"PATCH", jsonURL,{'Accept': 'text/plain', 'Content-Type': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"PUT", jsonURL,{'Accept': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"PATCH",jsonURL,{'Accept': 'text/plain'},{}),
|
||||
testFail(doJSONRequest,"GET", jsonURL,{'Accept': 'text/plain'}),
|
||||
testFail(doJSONRequest,"OPTIONS", jsonURL,{'Accept': 'text/plain'}),
|
||||
testFail(doJSONRequest,"DELETE",jsonURL,{'Accept': 'text/plain'})
|
||||
]));
|
||||
|
||||
QUnit.module("Task 2: doJSONRequest Response Validation", {});
|
||||
|
||||
QUnit.test("returns expected JSON body", testAll(
|
||||
[
|
||||
testStatusJSONBody(doJSONRequest,"PUT",baseURL+"/echo",headers,jsonBody_req,200,jsonBody_resp("PUT Working")),
|
||||
testStatusJSONBody(doJSONRequest,"POST",baseURL+"/new",headers,jsonBody_req,201,jsonBody_resp("POST Working")),
|
||||
testStatusJSONBody(doJSONRequest,"DELETE",baseURL,headers,undefined,204,{status: 204, text: 'DELETE Working'}),
|
||||
testStatusJSONBody(doJSONRequest,"GET",jsonURL,headers,undefined,200,{text: 'ok'}),
|
||||
testStatusJSONBody(doJSONRequest,"PATCH",jsonURL,headers,{},200,{text: 'ok'}),
|
||||
testStatusJSONBody(doJSONRequest,"GET",baseURL,headers,undefined,200,{text: 'GET Working'})
|
||||
]));
|
||||
|
||||
|
||||
|
||||
</script>
|
||||
</body>
|
||||
|
||||
</html>
|
|
@ -0,0 +1,103 @@
|
|||
function tests() {
|
||||
|
||||
test("App class constructors and methods", function(assert) {
|
||||
// The App class must be defined
|
||||
equal(typeof App === 'function', true, "The App class must be defined");
|
||||
equal(/^\s*class\s+/.test(App.toString()), true, "App is a function but it is not defined using the class keyword")
|
||||
|
||||
|
||||
// The App class constructor should throw an error if its argument is undefined
|
||||
assert.throws(function() {
|
||||
new App(undefined)
|
||||
}, "The App class constructor should throw an error if its argument is undefined")
|
||||
// The App class constructor should throw an error if its argument is not a canvas
|
||||
assert.throws(function() {
|
||||
new App("");
|
||||
}, "The App class constructor should throw an error if its argument is not an object")
|
||||
assert.throws(function() {
|
||||
new App(1);
|
||||
}, "The App class constructor should throw an error if its argument is a number")
|
||||
assert.throws(function() {
|
||||
new App([]);
|
||||
}, "The App class constructor should throw an error if its argument is an array")
|
||||
assert.throws(function() {
|
||||
new App(true);
|
||||
}, "The App class constructor should throw an error if its argument is a boolean")
|
||||
|
||||
// The default Stroke Style should be accessible in a static way, and should be equal to "black"
|
||||
equal(App.defaultStrokeStyle === 'black', true, 'The default Stroke Style should be accessible in a static way, and should be equal to "black"')
|
||||
|
||||
assert.throws(function() {
|
||||
new App({});
|
||||
}, "The App class constructor should throw an error if its argument options object is not pointing to a canvas element under the 'canvas' property")
|
||||
|
||||
const app = new App({canvas: 'test-canvas'})
|
||||
equal(app.strokeStyle, "black", "Getter for strokeStyle is not defined")
|
||||
|
||||
// The draw method must be defined
|
||||
equal(typeof app.draw === 'function', true, "The draw method must be defined")
|
||||
});
|
||||
|
||||
|
||||
test("History and Stroke object literals fields and methods", function(assert) {
|
||||
// The Stroke class must be defined
|
||||
equal(typeof Stroke === 'function', true, "The Stroke class must be defined");
|
||||
equal(/^\s*class\s+/.test(Stroke.toString()), true, "Stroke is a function but it is not defined using the class keyword")
|
||||
|
||||
equal(function(){
|
||||
try {
|
||||
const stroke = new Stroke('square')
|
||||
} catch (err) {
|
||||
return false
|
||||
}
|
||||
|
||||
return true
|
||||
}(), true, "Stroke can be instantiated")
|
||||
|
||||
const stroke = new Stroke('square');
|
||||
const stroke1 = new Stroke('circle');
|
||||
const stroke2 = new Stroke('triangle');
|
||||
history.initializeNewPath()
|
||||
|
||||
assert.throws(function() {
|
||||
history.push()
|
||||
}, "Must pass a Stroke instance when you push in the history")
|
||||
|
||||
equal(function() {
|
||||
try {
|
||||
history.push(stroke);
|
||||
} catch (err) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
}(), true, "History push accepts Stroke instances as a parameter");
|
||||
|
||||
equal(history.pop()[0] === stroke, true, "Pop returns an array containing the pushed Stroke instance")
|
||||
|
||||
history.initializeNewPath();
|
||||
|
||||
history.push(stroke);
|
||||
history.push(stroke1);
|
||||
history.push(stroke2);
|
||||
|
||||
equal(history.pop().length, 3, "Pop returns an array containing the pushed Stroke instances")
|
||||
|
||||
history.initializeNewPath();
|
||||
|
||||
equal(history.pop().length, 0, "Pop on an empty history should return an empty array")
|
||||
|
||||
history.initializeNewPath(); //simulate mouse down
|
||||
|
||||
history.push(stroke); //simulate mouse move
|
||||
history.push(stroke1); //simulate mouse move
|
||||
|
||||
history.initializeNewPath(); //simulate mouse up and down again
|
||||
|
||||
history.push(stroke2); //simulate mouse move
|
||||
|
||||
equal(history.pop().length, 1, "Pop returns an array containing the most recent path (Expected path with 1 Stroke)")
|
||||
|
||||
equal(history.pop().length, 2, "Pop returns an array containing the most recent path (Expected path with 2 Strokes)")
|
||||
|
||||
});
|
||||
}
|
|
@ -0,0 +1,24 @@
|
|||
/** @module root/router */
|
||||
'use strict';
|
||||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Favorite = mongoose.model('Favorite');
|
||||
|
||||
const { error, renderFav } = require('../utils');
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
Favorite.find({ bookmarked: true }, (err, favs) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
renderFav(req, res, favs);
|
||||
});
|
||||
});
|
||||
|
||||
/** router for /root */
|
||||
module.exports = router;
|
|
@ -0,0 +1,162 @@
|
|||
/** @module root/router */
|
||||
'use strict';
|
||||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const mongoose = require('mongoose');
|
||||
const Favorite = mongoose.model('Favorite');
|
||||
const { error, renderFav, parseId, notFound } = require('../utils');
|
||||
|
||||
function findAndRender(filter, req, res) {
|
||||
Favorite.find(filter, (err, favs) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
renderFav(req, res, favs);
|
||||
});
|
||||
}
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
if (!req.body.name || !req.body.dataURL) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad create form parameters');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: req.body.name,
|
||||
dataURL: req.body.dataURL,
|
||||
bookmarked: req.body.bookmarked
|
||||
}
|
||||
const favourite = new Favorite(data);
|
||||
|
||||
if (req.body._id) {
|
||||
favourite._id = req.body._id;
|
||||
} else {
|
||||
favourite._id = mongoose.Types.ObjectId();
|
||||
}
|
||||
|
||||
favourite.save((err, fav) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
res.status = 201;
|
||||
renderFav(req, res, fav, false);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
findAndRender({}, req, res);
|
||||
});
|
||||
|
||||
router.get('/search', (req, res) => {
|
||||
const filter = Object.assign({}, req.query);
|
||||
delete filter['dataURL'];
|
||||
delete filter['_method'];
|
||||
|
||||
findAndRender(filter, req, res);
|
||||
});
|
||||
|
||||
function findOne(id) {
|
||||
return (req, res) => {
|
||||
Favorite.findById(id(req), (err, fav) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderFav(req, res, fav, false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
router.get('/:id', findOne(req => parseId(req)));
|
||||
|
||||
function handleUpdate(partial = false) {
|
||||
return (req, res) => {
|
||||
|
||||
const edit = {};
|
||||
for (const key of ['dataURL', 'name']) {
|
||||
if (req.body[key]) {
|
||||
edit[key] = req.body[key];
|
||||
} else if (!partial) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad PUT form parameters');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.bookmarked !== undefined) {
|
||||
edit.bookmarked = req.body.bookmarked;
|
||||
}
|
||||
|
||||
Favorite.findByIdAndUpdate(parseId(req), { $set: edit }, {
|
||||
new: false,
|
||||
upsert: true,
|
||||
setDefaultsOnInsert: true,
|
||||
passRawResult: true,
|
||||
}, (err, fav) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
if (fav == null) {
|
||||
res.status = 201;
|
||||
findOne(() => parseId(req))(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status = 200;
|
||||
renderFav(req, res, fav, false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
router.put('/:id', handleUpdate());
|
||||
|
||||
router.patch('/:id', handleUpdate(true));
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
Favorite.findByIdAndDelete(parseId(req), (err, fav) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
res.format({
|
||||
json: () => res.writeHead(204),
|
||||
html: () => res.writeHead(302, { 'Location': '/favorites' })
|
||||
});
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/:id/bookmarked', (req, res) => {
|
||||
Favorite.findByIdAndUpdate(parseId(req), {
|
||||
$set: { bookmarked: req.body.bookmarked }
|
||||
}, { new: true }, (err, fav) => {
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.body.bookmarked) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad PUT bookmark form parameters');
|
||||
} else {
|
||||
renderFav(req, res, fav, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/** router for /root */
|
||||
module.exports = router;
|
|
@ -0,0 +1,164 @@
|
|||
/** @module root/router */
|
||||
'use strict';
|
||||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Favorite = mongoose.model('Favorite');
|
||||
|
||||
const { error, catchErrs, renderFav, parseId, notFound } = require('../utils');
|
||||
|
||||
async function findAndRender(filter, req, res) {
|
||||
try {
|
||||
const favs = await Favorite.find(filter);
|
||||
renderFav(req, res, favs);
|
||||
} catch(e) {
|
||||
error(e, res);
|
||||
}
|
||||
}
|
||||
|
||||
router.post('/', async (req, res) => {
|
||||
if (!req.body.name || !req.body.dataURL) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad create form parameters');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: req.body.name,
|
||||
dataURL: req.body.dataURL,
|
||||
bookmarked: req.body.bookmarked,
|
||||
};
|
||||
const favourite = new Favorite(data);
|
||||
|
||||
if (req.body._id) {
|
||||
favourite._id = req.body._id;
|
||||
} else {
|
||||
favourite._id = mongoose.Types.ObjectId();
|
||||
}
|
||||
|
||||
try {
|
||||
const fav = await favourite.save();
|
||||
res.status = 201;
|
||||
renderFav(req, res, fav, false);
|
||||
} catch(e) {
|
||||
error(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/', async (req, res) => {
|
||||
await findAndRender({}, req, res);
|
||||
});
|
||||
|
||||
router.get('/search', async (req, res) => {
|
||||
const filter = Object.assign({}, req.query);
|
||||
delete filter['dataURL'];
|
||||
delete filter['_method'];
|
||||
|
||||
await findAndRender(filter, req, res);
|
||||
});
|
||||
|
||||
function findOne(id) {
|
||||
return async (req, res) => {
|
||||
try {
|
||||
const fav = await Favorite.findById(id(req));
|
||||
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderFav(req, res, fav, false);
|
||||
} catch(e) {
|
||||
error(e, res);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
router.get('/:id', findOne(req => parseId(req)));
|
||||
|
||||
function handleUpdate(partial = false) {
|
||||
return async (req, res) => {
|
||||
const edit = {};
|
||||
for (const key of ['dataURL', 'name']) {
|
||||
if (req.body[key]) {
|
||||
edit[key] = req.body[key];
|
||||
} else if (!partial) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad PUT form parameters');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.bookmarked !== undefined) {
|
||||
edit.bookmarked = req.body.bookmarked;
|
||||
}
|
||||
|
||||
try {
|
||||
const fav = await Favorite.findByIdAndUpdate(parseId(req), { $set: edit }, {
|
||||
new: false,
|
||||
upsert: true,
|
||||
setDefaultsOnInsert: true,
|
||||
});
|
||||
|
||||
if (fav == null) {
|
||||
res.status = 201;
|
||||
await findOne(() => parseId(req))(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status = 200;
|
||||
renderFav(req, res, fav, false);
|
||||
} catch (e) {
|
||||
error(e, res);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
router.put('/:id', handleUpdate());
|
||||
|
||||
router.patch('/:id', handleUpdate(true));
|
||||
|
||||
router.delete('/:id', async (req, res) => {
|
||||
try {
|
||||
const fav = await Favorite.findByIdAndDelete(parseId(req));
|
||||
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
res.format({
|
||||
json: () => res.writeHead(204),
|
||||
html: () => res.writeHead(302, { 'Location': '/favorites' })
|
||||
});
|
||||
res.end();
|
||||
} catch (e) {
|
||||
error(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/:id/bookmarked', (req, res) => {
|
||||
try {
|
||||
const fav = Favorite.findByIdAndUpdate(parseId(req), {
|
||||
$set: { bookmarked: req.body.bookmarked }
|
||||
}, { new: true });
|
||||
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.body.bookmarked) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad PUT bookmark form parameters');
|
||||
} else {
|
||||
renderFav(req, res, fav, false);
|
||||
}
|
||||
} catch (e) {
|
||||
error(e, res);
|
||||
}
|
||||
});
|
||||
|
||||
/** router for /root */
|
||||
module.exports = router;
|
|
@ -0,0 +1,142 @@
|
|||
/** @module root/router */
|
||||
'use strict';
|
||||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const fs = require('fs');
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Favorite = mongoose.model('Favorite');
|
||||
|
||||
const { error, catchErrs, renderFav, parseId, notFound } = require('../utils');
|
||||
|
||||
function findAndRender(filter, req, res) {
|
||||
catchErrs(Favorite.find(filter), res).then(favs => {
|
||||
renderFav(req, res, favs);
|
||||
});
|
||||
}
|
||||
|
||||
router.post('/', (req, res) => {
|
||||
if (!req.body.name || !req.body.dataURL) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad create form parameters');
|
||||
return;
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: req.body.name,
|
||||
dataURL: req.body.dataURL,
|
||||
bookmarked: req.body.bookmarked,
|
||||
};
|
||||
const favourite = new Favorite(data);
|
||||
|
||||
if (req.body._id) {
|
||||
favourite._id = req.body._id;
|
||||
} else {
|
||||
favourite._id = mongoose.Types.ObjectId();
|
||||
}
|
||||
|
||||
catchErrs(favourite.save(), res).then(fav => {
|
||||
res.status = 201;
|
||||
renderFav(req, res, fav, false);
|
||||
});
|
||||
});
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
findAndRender({}, req, res);
|
||||
});
|
||||
|
||||
router.get('/search', (req, res) => {
|
||||
const filter = Object.assign({}, req.query);
|
||||
delete filter['dataURL'];
|
||||
delete filter['_method'];
|
||||
|
||||
findAndRender(filter, req, res);
|
||||
});
|
||||
|
||||
function findOne(id) {
|
||||
return (req, res) => {
|
||||
catchErrs(Favorite.findById(id(req)), res).then(fav => {
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
renderFav(req, res, fav, false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
router.get('/:id', findOne(req => parseId(req)));
|
||||
|
||||
function handleUpdate(partial = false) {
|
||||
return (req, res) => {
|
||||
const edit = {};
|
||||
for (const key of ['dataURL', 'name']) {
|
||||
if (req.body[key]) {
|
||||
edit[key] = req.body[key];
|
||||
} else if (!partial) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad PUT form parameters');
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (req.body.bookmarked !== undefined) {
|
||||
edit.bookmarked = req.body.bookmarked;
|
||||
}
|
||||
|
||||
catchErrs(Favorite.findByIdAndUpdate(parseId(req), { $set: edit }, {
|
||||
new: false,
|
||||
upsert: true,
|
||||
setDefaultsOnInsert: true,
|
||||
}), res).then(fav => {
|
||||
if (fav == null) {
|
||||
res.status = 201;
|
||||
findOne(() => parseId(req))(req, res);
|
||||
return;
|
||||
}
|
||||
|
||||
res.status = 200;
|
||||
renderFav(req, res, fav, false);
|
||||
});
|
||||
};
|
||||
}
|
||||
|
||||
router.put('/:id', handleUpdate());
|
||||
|
||||
router.patch('/:id', handleUpdate(true));
|
||||
|
||||
router.delete('/:id', (req, res) => {
|
||||
catchErrs(Favorite.findByIdAndDelete(parseId(req)), res).then(fav => {
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
res.format({
|
||||
json: () => res.writeHead(204),
|
||||
html: () => res.writeHead(302, { 'Location': '/favorites' })
|
||||
});
|
||||
res.end();
|
||||
});
|
||||
});
|
||||
|
||||
router.put('/:id/bookmarked', (req, res) => {
|
||||
catchErrs(Favorite.findByIdAndUpdate(parseId(req), {
|
||||
$set: { bookmarked: req.body.bookmarked }
|
||||
}, { new: true }), res).then(fav => {
|
||||
if (notFound(fav, res)) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!req.body.bookmarked) {
|
||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||
res.end('Bad PUT bookmark form parameters');
|
||||
} else {
|
||||
renderFav(req, res, fav, false);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
/** router for /root */
|
||||
module.exports = router;
|
|
@ -0,0 +1,72 @@
|
|||
'use strict';
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
//These routes should be activated under /test/fetch so that they can be called by the test.html
|
||||
//when testing your fetch wrappers doFetchRequest, doJSONRequest found in the public/js/fetch.js
|
||||
//Add the following to your app.js:
|
||||
//app.use('/test/fetch', routers.fetch_tests);
|
||||
|
||||
router.get('/', function(req, res, next) {
|
||||
if(req.accepts('json')) {
|
||||
res.json({
|
||||
text: 'GET Working'
|
||||
})
|
||||
} else {
|
||||
res.status(200).end('GET Working')
|
||||
}
|
||||
});
|
||||
|
||||
router.all('/json/ok', function(req, res, next) {
|
||||
res.json({
|
||||
text: 'ok'
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
router.delete('/', function(req, res, next) {
|
||||
if(req.accepts('json')) {
|
||||
res.json({
|
||||
status: 204,
|
||||
text: 'DELETE Working'
|
||||
})
|
||||
} else {
|
||||
res.status(204).end()
|
||||
}
|
||||
});
|
||||
|
||||
router.get('/redirect', function(req, res, next) {
|
||||
res.redirect('redirected')
|
||||
});
|
||||
|
||||
router.get('/redirected', function(req, res, next) {
|
||||
res.status(200).end('Redirect Works')
|
||||
});
|
||||
|
||||
router.post('/new', function(req, res, next) {
|
||||
if(req.accepts('json')) {
|
||||
res.type('json');
|
||||
res.set('Location', '24');
|
||||
res.status(201).json({
|
||||
text: 'POST Working',
|
||||
body: req.body
|
||||
})
|
||||
} else {
|
||||
res.set('Location', '42');
|
||||
res.status(201).end('POST Working\n'+req.body)
|
||||
}
|
||||
});
|
||||
|
||||
router.put('/echo', function(req, res, next) {
|
||||
if(req.accepts('json')) {
|
||||
res.json({
|
||||
text: 'PUT Working',
|
||||
body: req.body
|
||||
})
|
||||
} else {
|
||||
res.end(req.body)
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
|
@ -0,0 +1,24 @@
|
|||
/** @module root/router */
|
||||
'use strict';
|
||||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
const Favorite = mongoose.model('Favorite');
|
||||
|
||||
const { error } = require('../utils');
|
||||
|
||||
router.get('/', (req, res) => {
|
||||
Favorite.find({}, (err, favs) => {
|
||||
if (err) {
|
||||
return error(err, res);
|
||||
}
|
||||
|
||||
res.render('index.dust', { favs });
|
||||
});
|
||||
});
|
||||
|
||||
/** router for /root */
|
||||
module.exports = router;
|
|
@ -0,0 +1,35 @@
|
|||
/** @module routes/routers
|
||||
* Exposes all routers
|
||||
*/
|
||||
'use strict';
|
||||
|
||||
const fs = require('fs');
|
||||
|
||||
const dirEntries = fs.readdirSync(__dirname);
|
||||
const base = __dirname + '/';
|
||||
const routers = {};
|
||||
|
||||
try{
|
||||
dirEntries.forEach(function(dirEntry){
|
||||
const stats = fs.statSync(base + dirEntry);
|
||||
//try to load router of dir
|
||||
if(stats.isDirectory()){
|
||||
try{
|
||||
const router = require(base + dirEntry + '/router');
|
||||
//add router to our list of routers;
|
||||
routers[dirEntry] = router;
|
||||
}catch(err){
|
||||
console.log('Could not get router for ' + dirEntry);
|
||||
console.log(err.toString() + err.stack);
|
||||
}
|
||||
}
|
||||
});
|
||||
}catch(err){
|
||||
console.log('Error while loading routers');
|
||||
console.log(err.stack);
|
||||
//We don't know what happened, export empty object
|
||||
routers = {}
|
||||
}finally{
|
||||
module.exports = routers;
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
// vim: set ts=2 sw=2 et tw=80:
|
||||
|
||||
const mongoose = require('mongoose');
|
||||
|
||||
function catchErrs(promise, res) {
|
||||
return promise.catch(err => error(err, res));
|
||||
}
|
||||
|
||||
function error(err, res) {
|
||||
console.error(err);
|
||||
res.status = err instanceof mongoose.CastError ||
|
||||
err instanceof mongoose.TypeError ? 400 : 500;
|
||||
res.format({
|
||||
json: () => res.json({ error: err }),
|
||||
html: () => res.render('500.dust', { err: JSON.stringify(err, null, 2) }),
|
||||
});
|
||||
res.end();
|
||||
}
|
||||
|
||||
function renderFav(req, res, favs, list = true) {
|
||||
const makeTestsPass = e => {
|
||||
return {
|
||||
_id: e._id,
|
||||
name: e.name,
|
||||
dataURL: e.dataURL,
|
||||
bookmarked: '' + e.bookmarked
|
||||
}
|
||||
};
|
||||
|
||||
if (req.accepts('html')) {
|
||||
res.render(list ? 'favourites.dust' : 'favourite.dust',
|
||||
list ? { favs } : favs);
|
||||
} else if (req.accepts('json')) {
|
||||
if (list) {
|
||||
favs = favs.map(makeTestsPass);
|
||||
} else {
|
||||
favs = makeTestsPass(favs);
|
||||
}
|
||||
|
||||
res.json(favs);
|
||||
} else {
|
||||
res.writeHead(406);
|
||||
res.end();
|
||||
}
|
||||
}
|
||||
|
||||
function parseId(req) {
|
||||
if (typeof req.params.id === 'string' && req.params.id.length == 24) {
|
||||
return mongoose.Types.ObjectId(req.params.id);
|
||||
} else {
|
||||
return req.params.id;
|
||||
}
|
||||
}
|
||||
|
||||
function notFound(e, res) {
|
||||
if (e == null) {
|
||||
res.writeHead(404, {'Content-Type': 'text/plain'});
|
||||
res.end('Not found');
|
||||
return true;
|
||||
} else return false;
|
||||
}
|
||||
|
||||
module.exports = { error, renderFav, catchErrs, parseId, notFound };
|
||||
|
||||
|
|
@ -0,0 +1,9 @@
|
|||
/**
|
||||
* Standalond db seed
|
||||
*/
|
||||
var seed = require('./test/seed').seed;
|
||||
|
||||
seed(function(seedData){
|
||||
console.log("Seeding complete!")
|
||||
process.exit();
|
||||
})
|
|
@ -0,0 +1,56 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('../../config');
|
||||
var should = require('should');
|
||||
var seedDb = require('../seed');
|
||||
var request = require('supertest');
|
||||
|
||||
describe('Task 2: Testing Create /favorites routes', function(){
|
||||
describe('POST /favorites', function(){
|
||||
it('should create a new favorite if the request data is valid', function(done){
|
||||
var newFavData = {}
|
||||
newFavData[config.form._id] = "tt1",
|
||||
newFavData[config.form.name] = "NicePicture",
|
||||
newFavData[config.form.dataURL] = "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
newFavData[config.form.bookmarked] = "true"
|
||||
|
||||
request(config.url)
|
||||
.post('/favorites')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.send(newFavData)
|
||||
.expect('Content-Type', /json/, 'it should respond with Content-Type: application/json' )
|
||||
.expect(201)
|
||||
.end(function(err, res){
|
||||
var resFav = JSON.parse(res.text);
|
||||
should.equal(resFav[config.form._id], newFavData[config.form._id]);
|
||||
should.equal(resFav[config.form.dataURL], newFavData[config.form.dataURL]);
|
||||
should.equal(resFav[config.form.bookmarked], newFavData[config.form.bookmarked]);
|
||||
should.equal(resFav[config.form.name], newFavData[config.form.name]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should get a 400 Bad Request if data is invalid #1', function(done){
|
||||
var newFavData = {
|
||||
"invalid": "this object does not have the correct structure",
|
||||
};
|
||||
|
||||
request(config.url)
|
||||
.post('/favorites')
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.send(newFavData)
|
||||
.expect(400, done);
|
||||
});
|
||||
|
||||
it('should get a 400 Bad Request if data is not in json', function(done){
|
||||
request(config.url)
|
||||
.post('/favorites')
|
||||
.set('Content-Type', 'text/plain')
|
||||
.set('Accept', 'application/json')
|
||||
.send("This is a plain text request, it should result in a 400 bad request")
|
||||
.expect(400, done);
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,127 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('../../config');
|
||||
var should = require('should');
|
||||
var seedDb = require('../seed');
|
||||
var request = require('supertest');
|
||||
var favsOriginal = require('../seedData');
|
||||
var favs = []
|
||||
for (let i = 0; i < favsOriginal.length; i++) {
|
||||
let o = {}
|
||||
o[config.form._id] = favsOriginal[i]._id
|
||||
o[config.form.name] = favsOriginal[i].name
|
||||
o[config.form.dataURL] = favsOriginal[i].dataURL
|
||||
o[config.form.bookmarked] = favsOriginal[i].bookmarked
|
||||
favs.push(o)
|
||||
}
|
||||
|
||||
describe('Task 3: Testing Read for /favorites routes', function(){
|
||||
before(seed)
|
||||
|
||||
describe('GET /favorites', function(){
|
||||
it('should list all the favs with correct data', function(done){
|
||||
request(config.url)
|
||||
.get('/favorites')
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFavs = JSON.parse(res.text);
|
||||
console.log(resFavs);
|
||||
resFavs.forEach(function(fav) {
|
||||
for (let i = 0; i < favs.length; i++) {
|
||||
if(favs[i][config.form._id] == fav[config.form._id]) {
|
||||
should.equal(fav[config.form.dataURL], favs[i][config.form.dataURL]);
|
||||
should.equal(fav[config.form.bookmarked], favs[i][config.form.bookmarked]);
|
||||
should.equal(fav[config.form.name], favs[i][config.form.name]);
|
||||
}
|
||||
}
|
||||
})
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('GET /favorites/:favoriteid', function(){
|
||||
|
||||
it('should get the favorite with correct data', function(done){
|
||||
request(config.url)
|
||||
.get('/favorites/' + favs[1][config.form._id])
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFav = JSON.parse(res.text);
|
||||
should.equal(resFav[config.form._id], favs[1][config.form._id]);
|
||||
should.equal(resFav[config.form.dataURL], favs[1][config.form.dataURL]);
|
||||
should.equal(resFav[config.form.bookmarked], favs[1][config.form.bookmarked]);
|
||||
should.equal(resFav[config.form.name], favs[1][config.form.name]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 404 if the favorite does not exist', function(done){
|
||||
request(config.url)
|
||||
.get('/favorites/notValidId')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404, done);
|
||||
});
|
||||
});
|
||||
|
||||
describe(`GET /favorites/search`, function(){
|
||||
it(`should get the favorite with correct data: GET /favorites/search?${config.form._id}`, function(done){
|
||||
request(config.url)
|
||||
.get(`/favorites/search?${config.form._id}=${favs[1][config.form._id]}`)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFavArray = JSON.parse(res.text);
|
||||
should.equal(resFavArray.length, 1)
|
||||
var resFav = resFavArray[0]
|
||||
should.equal(resFav[config.form._id], favs[1][config.form._id]);
|
||||
should.equal(resFav[config.form.dataURL], favs[1][config.form.dataURL]);
|
||||
should.equal(resFav[config.form.bookmarked], favs[1][config.form.bookmarked]);
|
||||
should.equal(resFav[config.form.name], favs[1][config.form.name]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it(`should get the favorite with correct data: GET /favorites/search?${config.form._id}&${config.form.name}`, function(done){
|
||||
request(config.url)
|
||||
.get(`/favorites/search?${config.form._id}=${favs[5][config.form._id]}&${config.form.name}=${favs[5][config.form.name]}`)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFav = JSON.parse(res.text)[0];
|
||||
should.equal(resFav[config.form._id], favs[5][config.form._id]);
|
||||
should.equal(resFav[config.form.dataURL], favs[5][config.form.dataURL]);
|
||||
should.equal(resFav[config.form.bookmarked], favs[5][config.form.bookmarked]);
|
||||
should.equal(resFav[config.form.name], favs[5][config.form.name]);
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it(`should get empty array if there is no match: GET /favorites/search?${config.form.name}`, function(done){
|
||||
request(config.url)
|
||||
.get(`/favorites/search?&${config.form.name}=NoName`)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFavArray = JSON.parse(res.text);
|
||||
should.equal(resFavArray.length, 0)
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
});
|
||||
|
||||
function seed(done){
|
||||
//seed the db
|
||||
seedDb.seed(function(seedData){
|
||||
done();
|
||||
}, favs);
|
||||
}
|
|
@ -0,0 +1,44 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('../../config');
|
||||
var should = require('should');
|
||||
var seedDb = require('../seed');
|
||||
var request = require('supertest');
|
||||
var favs = require('../seedData');
|
||||
|
||||
describe('Task 4: Testing Update on /favorites routes', function(){
|
||||
describe('PUT /favorites/:favoriteid', function(){
|
||||
it('should change the name of an existing favorite', function(done){
|
||||
let reqBody = {}
|
||||
reqBody[config.form.name] = 'newName'
|
||||
reqBody[config.form.dataURL] = favs[3][config.form.dataURL] // maggicl: added to comply with assignment
|
||||
|
||||
request(config.url)
|
||||
.put('/favorites/' + favs[3]._id)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.send(reqBody)
|
||||
.expect(201)
|
||||
.end(function(err, res){
|
||||
let resPutFav = JSON.parse(res.text)
|
||||
should(resPutFav[config.form.name], 'newName')
|
||||
done()
|
||||
});
|
||||
});
|
||||
})
|
||||
|
||||
describe('GET /favorites/:favoriteid', function(){
|
||||
it('the name should be changed', function(done){
|
||||
request(config.url)
|
||||
.get('/favorites/' + favs[3]._id)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with Content-Type: application/json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var favGetFav = JSON.parse(res.text);
|
||||
should.equal(favGetFav[config.form.name], 'newName');
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -0,0 +1,60 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('../../config');
|
||||
var should = require('should');
|
||||
var seedDb = require('../seed');
|
||||
var request = require('supertest');
|
||||
var favs = require('../seedData')
|
||||
|
||||
describe('Task 5: Testing Delete for /favorites routes', function(){
|
||||
describe('DELETE /favorites/:favoriteid', function(){
|
||||
it('should delete an existing favorite', function(done){
|
||||
request(config.url)
|
||||
.del('/favorites/' + favs[1]._id)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(204)
|
||||
.end(function(err, res){
|
||||
res.text.should.be.empty;
|
||||
res.body.should.be.empty;
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should not list the previously deleted resource', function(done){
|
||||
request(config.url)
|
||||
.del('/favorites/' + favs[2]._id)
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
|
||||
request(config.url)
|
||||
.get('/favorites')
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFavs = JSON.parse(res.text);
|
||||
resFavs.forEach(function(fav){
|
||||
should.notEqual(fav[config.form._id], favs[2]._id);
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('should respond with a 404 for a previously deleted resource', function(done){
|
||||
request(config.url)
|
||||
.delete('/favorites/' + favs[1]._id)
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404, done);
|
||||
});
|
||||
|
||||
it('should respond with a 404 deleting a resource which does not exist', function(done){
|
||||
request(config.url)
|
||||
.delete('/favorites/invalidId')
|
||||
.set('Accept', 'application/json')
|
||||
.expect(404, done);
|
||||
});
|
||||
});
|
||||
|
||||
});
|
|
@ -0,0 +1,91 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('../../config');
|
||||
var should = require('should');
|
||||
var seedDb = require('../seed');
|
||||
var request = require('supertest');
|
||||
var favs = require('../seedData')
|
||||
|
||||
describe('Task 6: Testing /bookmarked routes and /favorites/:favoriteid/bookmarked', function(){
|
||||
|
||||
describe('GET /bookmarked', function(){
|
||||
it('should list only the bookmarked favs', function(done){
|
||||
request(config.url)
|
||||
.get('/bookmarked')
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFavs = JSON.parse(res.text);
|
||||
resFavs.forEach(function(fav){
|
||||
should.equal(fav[config.form.bookmarked], "true");
|
||||
});
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('PUT /favorites/:favoriteid/bookmarked', function(){
|
||||
after(drop)
|
||||
|
||||
it('initial bookmarked value should be false', function(done){
|
||||
request(config.url)
|
||||
.get(`/favorites/${favs[0]._id}`)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFav = JSON.parse(res.text);
|
||||
should(resFav[config.form.bookmarked], "false")
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('should change the bookmarked value', function(done){
|
||||
const reqBody = {}
|
||||
reqBody[config.form.bookmarked] = "true"
|
||||
|
||||
request(config.url)
|
||||
.put(`/favorites/${favs[0]._id}/bookmarked`)
|
||||
.set('Content-Type', 'application/json')
|
||||
.set('Accept', 'application/json')
|
||||
.send(reqBody)
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFav = JSON.parse(res.text);
|
||||
should(resFav[config.form.bookmarked], "true")
|
||||
done();
|
||||
});
|
||||
});
|
||||
|
||||
it('bookmarked value should be changed', function(done){
|
||||
request(config.url)
|
||||
.get(`/favorites/${favs[0]._id}`)
|
||||
.set('Accept', 'application/json')
|
||||
.expect('Content-Type', /json/, 'it should respond with json' )
|
||||
.expect(200)
|
||||
.end(function(err, res){
|
||||
var resFav = JSON.parse(res.text);
|
||||
should(resFav[config.form.bookmarked], "true")
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
function drop(done){
|
||||
let deleteIds = ['t0','t3','t4','t5', 'tt1']
|
||||
let count = 0
|
||||
|
||||
for (let i = 0; i < deleteIds.length; i++) {
|
||||
request(config.url)
|
||||
.delete(`/favorites/${deleteIds[i]}`)
|
||||
.end(function() {
|
||||
count++
|
||||
if(count == deleteIds.length) {
|
||||
done()
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
|
@ -0,0 +1,46 @@
|
|||
'use strict';
|
||||
|
||||
var config = require('../config');
|
||||
var request = require('request');
|
||||
|
||||
//seedData
|
||||
var seedData = require('./seedData')
|
||||
|
||||
//total callbacks (one for each model)
|
||||
var totalCbs = 0;
|
||||
var cbCnt = 0;
|
||||
|
||||
/**
|
||||
* Recursive function that goes through
|
||||
* seedData populating each item of it
|
||||
*/
|
||||
var seedModel = function(done, s){
|
||||
if(s != undefined) {
|
||||
seedData = s
|
||||
}
|
||||
totalCbs = seedData.length
|
||||
|
||||
for (let i = 0; i < seedData.length; i++) {
|
||||
const form = {}
|
||||
form[config.form._id] = seedData[i]._id
|
||||
form[config.form.name] = seedData[i].name
|
||||
form[config.form.dataURL] = seedData[i].dataURL
|
||||
form[config.form.bookmarked] = seedData[i].bookmarked
|
||||
|
||||
request.post(`${config.url}/favorites`, {
|
||||
form: form
|
||||
}, function(error, response, body){
|
||||
cbCnt++
|
||||
if(cbCnt == totalCbs) {
|
||||
done(seedData)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* This is where everything starts
|
||||
*/
|
||||
module.exports.seed = function (done, s){
|
||||
seedModel(done, s)
|
||||
}
|
|
@ -0,0 +1,47 @@
|
|||
'use strict';
|
||||
|
||||
var favorites = [
|
||||
{
|
||||
"_id" : "t0",
|
||||
"name" : "My Nice Image",
|
||||
"dataURL" : "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
"bookmarked" : "false"
|
||||
},
|
||||
|
||||
{
|
||||
"_id" : "t1",
|
||||
"name" : "My Nice Image 1",
|
||||
"dataURL" : "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
"bookmarked" : "true"
|
||||
},
|
||||
|
||||
{
|
||||
"_id" : "t2",
|
||||
"name" : "My Nice Image 2",
|
||||
"dataURL" : "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
"bookmarked" : "false"
|
||||
},
|
||||
|
||||
{
|
||||
"_id" : "t3",
|
||||
"name" : "My Nice Image 3",
|
||||
"dataURL" : "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
"bookmarked" : "true"
|
||||
},
|
||||
|
||||
{
|
||||
"_id" : "t4",
|
||||
"name" : "My Nice Image 4",
|
||||
"dataURL" : "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
"bookmarked" : "false"
|
||||
},
|
||||
|
||||
{
|
||||
"_id" : "t5",
|
||||
"name" : "MyNiceImage5",
|
||||
"dataURL" : "data:image/svg+xml;base64,PD94bWwgdmVyc2lvbj0iMS4wIiBlbmNvZGluZz0idXRmLTgiPz4NCjwhLS0gR2VuZXJhdG9yOiBBZG9iZSBJbGx1c3RyYXRvciAyMS4xLjAsIFNWRyBFeHBvcnQgUGx1Zy1JbiAuIFNWRyBWZXJzaW9uOiA2LjAwIEJ1aWxkIDApICAtLT4NCjxzdmcgdmVyc2lvbj0iMS4xIiBpZD0iTGF5ZXJfMSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayIgeD0iMHB4IiB5PSIwcHgiDQoJIHZpZXdCb3g9IjAgMCAyMDAgMjAwIiBzdHlsZT0iZW5hYmxlLWJhY2tncm91bmQ6bmV3IDAgMCAyMDAgMjAwOyIgeG1sOnNwYWNlPSJwcmVzZXJ2ZSI+DQo8Zz4NCgk8cGF0aCBkPSJNMTgwLDQxLjdoLTUxLjFWMzEuMWg0Mi4yQzE1My4xLDEyLjUsMTI3LjgsMSw5OS45LDFDNDUuMiwxLjEsMC45LDQ1LjQsMSwxMDAuMWMwLDI2LjgsMTAuNyw1MS4xLDI4LjEsNjguOQ0KCQljLTEuNC0yLTIuNC00LjEtMy02LjFjLTEtMy41LTEtNS45LTEuMS0xNS44di00Ny4xaDE0Ljh2NDguNWMwLDMuNC0wLjEsNi42LDEsOS42YzMsNy42LDExLjMsOC41LDE2LDguNWMyLjMsMCw4LjMtMC4xLDEyLjYtMy45DQoJCWM0LjQtMy45LDQuNC04LjQsNC40LTE1di00Ny43aDE0Ljl2NDkuN2MtMC4xLDguOS0wLjEsMTYuMy04LjUsMjMuNWMtOCw3LTE4LjQsNy43LTIzLjgsNy43Yy00LjgsMC05LjUtMC42LTE0LTIuMQ0KCQljLTEuOC0wLjYtMy41LTEuNC01LTIuM2MxNy4xLDE0LDM5LDIyLjQsNjIuOCwyMi40YzU0LjctMC4xLDk5LTQ0LjQsOTguOS05OS4xQzE5OSw3OC4xLDE5MS45LDU4LDE4MCw0MS43eiBNMTc1LjMsOTQuNGwtNi4zLTkNCgkJYzIuMS0xLjQsOC43LTUuNyw4LjctMTcuN2MwLTItMC4yLTQuMS0xLTYuMmMtMS43LTQuMS00LjYtNC45LTYuNi00LjljLTMuNiwwLTQuOSwyLjUtNS43LDQuM2MtMC41LDEuMy0wLjYsMS41LTEuOCw2LjZsLTEuNSw2LjkNCgkJYy0wLjksMy42LTEuMyw1LjQtMiw3LjJjLTEuMSwyLjYtNC41LDkuNi0xNCw5LjZjLTEwLjksMC0xNy44LTkuMi0xNy44LTIyLjZjMC0xMi4zLDYuMS0xOSwxMS42LTIzbDYuNiw4LjgNCgkJYy0yLjgsMS45LTguNSw1LjctOC41LDE0LjhjMCw1LjgsMi42LDEwLjgsNywxMC44YzQuOSwwLDUuOC01LjMsNi44LTEwLjVsMS4zLTUuOWMxLjYtNy43LDQuOC0xOC42LDE3LTE4LjYNCgkJYzEzLjEsMCwxOC40LDEyLjIsMTguNCwyNC4zYzAsMy4yLTAuMyw2LjctMS4zLDEwLjJDMTg1LjEsODMuNCwxODIuMyw5MC4xLDE3NS4zLDk0LjR6Ii8+DQo8L2c+DQo8L3N2Zz4NCg==",
|
||||
"bookmarked" : "true"
|
||||
}
|
||||
]
|
||||
|
||||
module.exports = favorites;
|
|
@ -0,0 +1,14 @@
|
|||
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset='utf-8'>
|
||||
</head>
|
||||
<body>
|
||||
<h1>Error 500</h1>
|
||||
<pre>
|
||||
{err}
|
||||
</pre>
|
||||
</body>
|
||||
</html>
|
||||
|
|
@ -0,0 +1,13 @@
|
|||
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>{name}</title>
|
||||
</head>
|
||||
<body>
|
||||
{>"favourite_partial" /}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -0,0 +1,28 @@
|
|||
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||
|
||||
<h3>{name}</h3>
|
||||
<img src="{dataURL}" alt="{name}">
|
||||
{?b}
|
||||
<p>
|
||||
<strong>Bookmarked</strong>
|
||||
</p>
|
||||
{/b}
|
||||
{?details}
|
||||
<a href="/favorites/{_id}">Details</a>
|
||||
{:else}
|
||||
<form method="POST" action="/favorites/{_id}?_method=PUT">
|
||||
<input type="hidden" name="dataURL" value="{dataURL}">
|
||||
<label for="name">Name:</label>
|
||||
<input type="text" name="name" placeholder="Name" value="{name}"><br>
|
||||
<button>Update</button><br>
|
||||
<button formaction="/favorites/{_id}?_method=DELETE">Delete</button><br>
|
||||
{?bookmarked}
|
||||
<button name="bookmarked" value="false"
|
||||
formaction="/favorites/{_id}/bookmarked?_method=PUT">Remove bookmark</button>
|
||||
{:else}
|
||||
<button name="bookmarked" value="true"
|
||||
formaction="/favorites/{_id}/bookmarked?_method=PUT">Add bookmark</button>
|
||||
{/bookmarked}
|
||||
</form>
|
||||
<a href="/favorites">Favourites list</a>
|
||||
{/details}
|
|
@ -0,0 +1,28 @@
|
|||
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
{?bookmarked}
|
||||
<title>Bookmarked</title>
|
||||
{:else}
|
||||
<title>Favourites</title>
|
||||
{/bookmarked}
|
||||
</head>
|
||||
<body>
|
||||
{?bookmarked}
|
||||
<h1>Bookmarked</h1>
|
||||
{:else}
|
||||
<h1>Favourites</h1>
|
||||
{/bookmarked}
|
||||
{#favs}
|
||||
<div>
|
||||
{>"favourite_partial" name=name dataURL=dataURL _id=_id bookmarked=bookmarked details="true" /}
|
||||
</div>
|
||||
{:else}
|
||||
<strong>No favourites.</strong>
|
||||
{/favs}
|
||||
</body>
|
||||
</html>
|
||||
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>OO-JS Exercise - Web Atelier 2017</title>
|
||||
<link rel="stylesheet" href="https://maxcdn.bootstrapcdn.com/font-awesome/4.6.3/css/font-awesome.min.css">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
|
||||
<body onload="init()">
|
||||
<h1>
|
||||
OO-JS Exercise: Canvas
|
||||
</h1>
|
||||
<div id="app">
|
||||
<div id="left-toolbar" class="toolbar">
|
||||
<button id="clear-btn">Clear</button>
|
||||
<button id="undo-btn">Undo</button>
|
||||
<button id="camera-btn"><i class="fa fa-camera" aria-hidden="true"></i></button>
|
||||
</div>
|
||||
<canvas id="canvas" width="600" height="400"></canvas>
|
||||
<div id="brush-toolbar" class="toolbar">
|
||||
<!-- Brushes buttons go here (programmatically). Each button should be a <button> element -->
|
||||
</div>
|
||||
</div>
|
||||
<h2>Favourites</h2>
|
||||
<div id="favourites">
|
||||
{#favs}
|
||||
{>"favourite_partial" name=name dataURL=dataURL _id=_id bookmarked=bookmarked details="true" /}
|
||||
{/favs}
|
||||
</div>
|
||||
<script src="scripts/brushes.js"></script>
|
||||
<script src="scripts/undo.js"></script>
|
||||
<!-- <script src="scripts/clock.js"></script> -->
|
||||
<script src="scripts/app.js"></script>
|
||||
<script src="main.js"></script>
|
||||
</body>
|
||||
|
||||
</html>
|
File diff suppressed because it is too large
Load Diff
Reference in New Issue