Merge branch 'master' of git.maggioni.xyz:maggicl/SA3
This commit is contained in:
commit
248ca3449c
58 changed files with 32331 additions and 46 deletions
BIN
.DS_Store
vendored
BIN
.DS_Store
vendored
Binary file not shown.
|
@ -50,10 +50,18 @@ fs.readFile(__dirname + '/db.json', { encoding: 'utf8' }, (e, data) => {
|
||||||
app.use('/bookmarked', routers.bookmarked);
|
app.use('/bookmarked', routers.bookmarked);
|
||||||
});
|
});
|
||||||
|
|
||||||
app.locals.writeFavs = () => {
|
let writing = false;
|
||||||
|
app.locals.writeFavs = (done) => {
|
||||||
|
if (writing) {
|
||||||
|
setTimeout(() => app.locals.writeFavs(done), 100);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
writing = true;
|
||||||
fs.writeFile(__dirname + '/db.json', JSON.stringify(app.locals.favourites),
|
fs.writeFile(__dirname + '/db.json', JSON.stringify(app.locals.favourites),
|
||||||
{ encoding: 'utf8' }, err => {
|
{ encoding: 'utf8' }, err => {
|
||||||
if (err) console.error(err);
|
if (err) console.error(err);
|
||||||
|
writing = false;
|
||||||
|
done();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -8,8 +8,9 @@ const router = express.Router();
|
||||||
|
|
||||||
let id = 1;
|
let id = 1;
|
||||||
|
|
||||||
function nextId() {
|
function nextId(favs) {
|
||||||
return id++;
|
while (favs.some((newId => e => e._id == newId)(++id)));
|
||||||
|
return id;
|
||||||
}
|
}
|
||||||
|
|
||||||
function createFav(req, res) {
|
function createFav(req, res) {
|
||||||
|
@ -20,26 +21,39 @@ function createFav(req, res) {
|
||||||
}
|
}
|
||||||
|
|
||||||
const favourite = {
|
const favourite = {
|
||||||
_id: req.body._id ? req.body._id : nextId(),
|
_id: req.body._id ? req.body._id : nextId(req.app.locals.favourites),
|
||||||
name: req.body.name,
|
name: req.body.name,
|
||||||
dataURL: req.body.dataURL,
|
dataURL: req.body.dataURL,
|
||||||
bookmarked: req.body.bookmarked,
|
bookmarked: req.body.bookmarked,
|
||||||
};
|
};
|
||||||
|
|
||||||
req.app.locals.favourites.push(favourite);
|
req.app.locals.favourites.push(favourite);
|
||||||
req.app.locals.writeFavs();
|
req.app.locals.writeFavs(() => {
|
||||||
|
|
||||||
res.status = 201;
|
res.status = 201;
|
||||||
renderFav(req, res, favourite, false);
|
renderFav(req, res, favourite, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
router.post('/', createFav);
|
router.post('/', createFav);
|
||||||
|
|
||||||
function renderFav(req, res, favs, list = true) {
|
function renderFav(req, res, favs, list = true) {
|
||||||
if (req.accepts('html')) {
|
if (req.accepts('html')) {
|
||||||
res.render(list ? 'favourites.dust' : 'favourite.dust',
|
let ctx;
|
||||||
list ? { favs: favs } : Object.assign({b: favs.bookmarked === 'true' ||
|
if (list) {
|
||||||
favs.bookmarked === true}, favs));
|
const f = [];
|
||||||
|
for (const e of favs) {
|
||||||
|
f.push(Object.assign({
|
||||||
|
b: e.bookmarked === 'true' || e.bookmarked === true
|
||||||
|
}, e));
|
||||||
|
}
|
||||||
|
ctx = {favs: f};
|
||||||
|
} else {
|
||||||
|
ctx = Object.assign({
|
||||||
|
b: favs.bookmarked === 'true' || favs.bookmarked === true
|
||||||
|
}, favs);
|
||||||
|
}
|
||||||
|
|
||||||
|
res.render(list ? 'favourites.dust' : 'favourite.dust', ctx);
|
||||||
} else if (req.accepts('json')) {
|
} else if (req.accepts('json')) {
|
||||||
res.json(favs);
|
res.json(favs);
|
||||||
} else {
|
} else {
|
||||||
|
@ -76,7 +90,8 @@ router.get('/:id', (req, res) => {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
router.put('/:id', (req, res) => {
|
function handleUpdate(partial = false) {
|
||||||
|
return (req, res) => {
|
||||||
const edit = req.app.locals.favourites
|
const edit = req.app.locals.favourites
|
||||||
.find(e => e._id == req.params.id);
|
.find(e => e._id == req.params.id);
|
||||||
|
|
||||||
|
@ -86,20 +101,27 @@ router.put('/:id', (req, res) => {
|
||||||
for (const key of ['dataURL', 'name']) {
|
for (const key of ['dataURL', 'name']) {
|
||||||
if (req.body[key]) {
|
if (req.body[key]) {
|
||||||
edit[key] = req.body[key];
|
edit[key] = req.body[key];
|
||||||
} else {
|
} else if (!partial) {
|
||||||
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
res.writeHead(400, { 'Content-Type': 'text/plain' });
|
||||||
res.end('Bad PUT form parameters');
|
res.end('Bad PUT form parameters');
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
edit.bookmarked = !!req.body.bookmarked;
|
if (req.body.bookmarked !== undefined) {
|
||||||
req.app.locals.writeFavs();
|
edit.bookmarked = req.body.bookmarked;
|
||||||
|
}
|
||||||
|
req.app.locals.writeFavs(() => {
|
||||||
res.status = 200;
|
res.status = 200;
|
||||||
renderFav(req, res, edit, false);
|
renderFav(req, res, edit, false);
|
||||||
}
|
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
router.put('/:id', handleUpdate());
|
||||||
|
router.patch('/:id', handleUpdate(true));
|
||||||
|
|
||||||
|
|
||||||
router.delete('/:id', (req, res) => {
|
router.delete('/:id', (req, res) => {
|
||||||
let idx = -1;
|
let idx = -1;
|
||||||
|
@ -116,13 +138,13 @@ router.delete('/:id', (req, res) => {
|
||||||
return;
|
return;
|
||||||
} else {
|
} else {
|
||||||
req.app.locals.favourites.splice(idx, 1);
|
req.app.locals.favourites.splice(idx, 1);
|
||||||
req.app.locals.writeFavs();
|
req.app.locals.writeFavs(() => {
|
||||||
|
|
||||||
res.format({
|
res.format({
|
||||||
json: () => res.writeHead(204),
|
json: () => res.writeHead(204),
|
||||||
html: () => res.writeHead(302, { 'Location': '/favorites' })
|
html: () => res.writeHead(302, { 'Location': '/favorites' })
|
||||||
});
|
});
|
||||||
res.end();
|
res.end();
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -138,10 +160,10 @@ router.put('/:id/bookmarked', (req, res) => {
|
||||||
res.end('Bad PUT bookmark form parameters');
|
res.end('Bad PUT bookmark form parameters');
|
||||||
} else {
|
} else {
|
||||||
edit.bookmarked = req.body.bookmarked;
|
edit.bookmarked = req.body.bookmarked;
|
||||||
req.app.locals.writeFavs();
|
req.app.locals.writeFavs(() => {
|
||||||
|
|
||||||
res.status = 200;
|
res.status = 200;
|
||||||
renderFav(req, res, edit, false);
|
renderFav(req, res, edit, false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{! vim: set ft=html ts=2 sw=2 et tw=120: !}
|
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
{! vim: set ft=html ts=2 sw=2 et tw=120: !}
|
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||||
|
|
||||||
<h3>{name}</h3>
|
<h3>{name}</h3>
|
||||||
<img src="{dataURL}" alt="{name}">
|
<img src="{dataURL}" alt="{name}">
|
||||||
|
{?b}
|
||||||
|
<p>
|
||||||
|
<strong>Bookmarked</strong>
|
||||||
|
</p>
|
||||||
|
{/b}
|
||||||
{?details}
|
{?details}
|
||||||
<a href="/favorites/{_id}">Details</a>
|
<a href="/favorites/{_id}">Details</a>
|
||||||
{:else}
|
{:else}
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{! vim: set ft=html ts=2 sw=2 et tw=120: !}
|
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||||
|
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
|
@ -17,7 +17,7 @@
|
||||||
{/bookmarked}
|
{/bookmarked}
|
||||||
{#favs}
|
{#favs}
|
||||||
<div>
|
<div>
|
||||||
{>"favourite_partial" name=name dataURL=dataURL _id=_id details="true" /}
|
{>"favourite_partial" name=name dataURL=dataURL _id=_id b=b details="true" /}
|
||||||
</div>
|
</div>
|
||||||
{:else}
|
{:else}
|
||||||
<strong>No favourites.</strong>
|
<strong>No favourites.</strong>
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
{! vim: set ft=html ts=2 sw=2 et tw=120: !}
|
{! vim: set ts=2 sw=2 et tw=120: !}
|
||||||
|
|
||||||
<!DOCTYPE html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
|
|
BIN
hw6/Claudio_Maggioni.zip
Normal file
BIN
hw6/Claudio_Maggioni.zip
Normal file
Binary file not shown.
1
hw6/Claudio_Maggioni/.gitignore
vendored
Normal file
1
hw6/Claudio_Maggioni/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
||||||
|
db.json
|
47
hw6/Claudio_Maggioni/app.js
Normal file
47
hw6/Claudio_Maggioni/app.js
Normal file
|
@ -0,0 +1,47 @@
|
||||||
|
// 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());
|
||||||
|
|
||||||
|
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);
|
||||||
|
|
||||||
|
module.exports = app;
|
9
hw6/Claudio_Maggioni/bin/www
Executable file
9
hw6/Claudio_Maggioni/bin/www
Executable file
|
@ -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);
|
||||||
|
});
|
11
hw6/Claudio_Maggioni/config.js
Normal file
11
hw6/Claudio_Maggioni/config.js
Normal file
|
@ -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
|
||||||
|
}
|
||||||
|
}
|
20
hw6/Claudio_Maggioni/models/Favorites.js
Normal file
20
hw6/Claudio_Maggioni/models/Favorites.js
Normal file
|
@ -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);
|
||||||
|
|
||||||
|
|
||||||
|
|
36
hw6/Claudio_Maggioni/package.json
Normal file
36
hw6/Claudio_Maggioni/package.json
Normal file
|
@ -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"
|
||||||
|
}
|
||||||
|
}
|
15
hw6/Claudio_Maggioni/public/main.js
Normal file
15
hw6/Claudio_Maggioni/public/main.js
Normal file
|
@ -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'
|
||||||
|
});
|
||||||
|
}
|
5
hw6/Claudio_Maggioni/public/qunit-compat.js
Normal file
5
hw6/Claudio_Maggioni/public/qunit-compat.js
Normal file
|
@ -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);
|
5588
hw6/Claudio_Maggioni/public/resources/jsverify.standalone.js
Normal file
5588
hw6/Claudio_Maggioni/public/resources/jsverify.standalone.js
Normal file
File diff suppressed because it is too large
Load diff
17084
hw6/Claudio_Maggioni/public/resources/lodash.js
Normal file
17084
hw6/Claudio_Maggioni/public/resources/lodash.js
Normal file
File diff suppressed because it is too large
Load diff
5048
hw6/Claudio_Maggioni/public/resources/qunit-2.4.0.js
Normal file
5048
hw6/Claudio_Maggioni/public/resources/qunit-2.4.0.js
Normal file
File diff suppressed because it is too large
Load diff
235
hw6/Claudio_Maggioni/public/resources/qunit.css
Normal file
235
hw6/Claudio_Maggioni/public/resources/qunit.css
Normal file
|
@ -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;
|
||||||
|
}
|
259
hw6/Claudio_Maggioni/public/scripts/app.js
Normal file
259
hw6/Claudio_Maggioni/public/scripts/app.js
Normal file
|
@ -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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
53
hw6/Claudio_Maggioni/public/scripts/brushes.js
Normal file
53
hw6/Claudio_Maggioni/public/scripts/brushes.js
Normal file
|
@ -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();
|
||||||
|
}
|
||||||
|
}
|
37
hw6/Claudio_Maggioni/public/scripts/undo.js
Normal file
37
hw6/Claudio_Maggioni/public/scripts/undo.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
133
hw6/Claudio_Maggioni/public/style.css
Normal file
133
hw6/Claudio_Maggioni/public/style.css
Normal file
|
@ -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;
|
||||||
|
}
|
26
hw6/Claudio_Maggioni/public/test.html
Normal file
26
hw6/Claudio_Maggioni/public/test.html
Normal file
|
@ -0,0 +1,26 @@
|
||||||
|
<!DOCTYPE html>
|
||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<meta charset="utf-8">
|
||||||
|
<title>Exercise 3 - Object-oriented Javascript</title>
|
||||||
|
<link rel="stylesheet" href="resources/qunit.css">
|
||||||
|
</head>
|
||||||
|
<body>
|
||||||
|
<canvas id="test-canvas" width="600" height="400" style="display: none"></canvas>
|
||||||
|
|
||||||
|
<div id="qunit"></div>
|
||||||
|
<div id="qunit-fixture"></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="resources/qunit-2.4.0.js"></script>
|
||||||
|
<script src="resources/lodash.js"></script>
|
||||||
|
<script src="resources/jsverify.standalone.js"></script>
|
||||||
|
<script src="qunit-compat.js"></script>
|
||||||
|
<script src="test.js"></script>
|
||||||
|
<script>
|
||||||
|
tests();
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
103
hw6/Claudio_Maggioni/public/test.js
Normal file
103
hw6/Claudio_Maggioni/public/test.js
Normal file
|
@ -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)")
|
||||||
|
|
||||||
|
});
|
||||||
|
}
|
2
hw6/Claudio_Maggioni/readme.md
Normal file
2
hw6/Claudio_Maggioni/readme.md
Normal file
|
@ -0,0 +1,2 @@
|
||||||
|
# Bonuses implemented:
|
||||||
|
- *Excercise 4*, Use mongoose and MongoDB with async/await
|
24
hw6/Claudio_Maggioni/routes/bookmarked/router.js
Normal file
24
hw6/Claudio_Maggioni/routes/bookmarked/router.js
Normal file
|
@ -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;
|
162
hw6/Claudio_Maggioni/routes/favourites_db/router.js
Normal file
162
hw6/Claudio_Maggioni/routes/favourites_db/router.js
Normal file
|
@ -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;
|
164
hw6/Claudio_Maggioni/routes/favourites_db_asaw/router.js
Normal file
164
hw6/Claudio_Maggioni/routes/favourites_db_asaw/router.js
Normal file
|
@ -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;
|
142
hw6/Claudio_Maggioni/routes/favourites_db_promises/router.js
Normal file
142
hw6/Claudio_Maggioni/routes/favourites_db_promises/router.js
Normal file
|
@ -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;
|
24
hw6/Claudio_Maggioni/routes/root/router.js
Normal file
24
hw6/Claudio_Maggioni/routes/root/router.js
Normal file
|
@ -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;
|
35
hw6/Claudio_Maggioni/routes/routers.js
Normal file
35
hw6/Claudio_Maggioni/routes/routers.js
Normal file
|
@ -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;
|
||||||
|
}
|
||||||
|
|
65
hw6/Claudio_Maggioni/routes/utils.js
Normal file
65
hw6/Claudio_Maggioni/routes/utils.js
Normal file
|
@ -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 };
|
||||||
|
|
||||||
|
|
9
hw6/Claudio_Maggioni/seed.js
Normal file
9
hw6/Claudio_Maggioni/seed.js
Normal file
|
@ -0,0 +1,9 @@
|
||||||
|
/**
|
||||||
|
* Standalond db seed
|
||||||
|
*/
|
||||||
|
var seed = require('./test/seed').seed;
|
||||||
|
|
||||||
|
seed(function(seedData){
|
||||||
|
console.log("Seeding complete!")
|
||||||
|
process.exit();
|
||||||
|
})
|
56
hw6/Claudio_Maggioni/test/routes/2.favorite.create.js
Normal file
56
hw6/Claudio_Maggioni/test/routes/2.favorite.create.js
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
127
hw6/Claudio_Maggioni/test/routes/3.favorite.read.js
Normal file
127
hw6/Claudio_Maggioni/test/routes/3.favorite.read.js
Normal file
|
@ -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);
|
||||||
|
}
|
44
hw6/Claudio_Maggioni/test/routes/4.favorite.update.js
Normal file
44
hw6/Claudio_Maggioni/test/routes/4.favorite.update.js
Normal file
|
@ -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();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
||||||
|
});
|
60
hw6/Claudio_Maggioni/test/routes/5.favorite.delete.js
Normal file
60
hw6/Claudio_Maggioni/test/routes/5.favorite.delete.js
Normal file
|
@ -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);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
91
hw6/Claudio_Maggioni/test/routes/6.bookmarked.read.js
Normal file
91
hw6/Claudio_Maggioni/test/routes/6.bookmarked.read.js
Normal file
|
@ -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()
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
46
hw6/Claudio_Maggioni/test/seed.js
Normal file
46
hw6/Claudio_Maggioni/test/seed.js
Normal file
|
@ -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)
|
||||||
|
}
|
47
hw6/Claudio_Maggioni/test/seedData.js
Normal file
47
hw6/Claudio_Maggioni/test/seedData.js
Normal file
|
@ -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;
|
14
hw6/Claudio_Maggioni/views/500.dust
Normal file
14
hw6/Claudio_Maggioni/views/500.dust
Normal file
|
@ -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>
|
||||||
|
|
13
hw6/Claudio_Maggioni/views/favourite.dust
Normal file
13
hw6/Claudio_Maggioni/views/favourite.dust
Normal file
|
@ -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>
|
||||||
|
|
||||||
|
|
28
hw6/Claudio_Maggioni/views/favourite_partial.dust
Normal file
28
hw6/Claudio_Maggioni/views/favourite_partial.dust
Normal file
|
@ -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}
|
28
hw6/Claudio_Maggioni/views/favourites.dust
Normal file
28
hw6/Claudio_Maggioni/views/favourites.dust
Normal file
|
@ -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>
|
||||||
|
|
||||||
|
|
41
hw6/Claudio_Maggioni/views/index.dust
Normal file
41
hw6/Claudio_Maggioni/views/index.dust
Normal file
|
@ -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>
|
2318
hw6/Claudio_Maggioni/yarn.lock
Normal file
2318
hw6/Claudio_Maggioni/yarn.lock
Normal file
File diff suppressed because it is too large
Load diff
Reference in a new issue