Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 27 additions & 3 deletions models/author.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
var mongoose = require('mongoose');
var moment = require('moment');

var Schema = mongoose.Schema;

Expand All @@ -9,26 +10,49 @@ var AuthorSchema = new Schema({
date_of_death: { type: Date }
});

// Virtual for author's full name
AuthorSchema
.virtual('name')
.get(function () {
return this.family_name + ', ' + this,first_name;
});

// Virtual for author's lifespan
AuthorSchema
.virtual('lifespan')
.get(function () {
return (this.date_of_death.getYear() - this.date_of_birth.getYear()).toString();
});

// Virtual for author's URL
AuthorSchema
.virtual('url')
.get(function() {
return '/catalog/author/' + this._id;
});

AuthorSchema
.virtual('url')
.get(function () {
var lifetime_string='';
if (this.date_of_birth) {
lifetime_string = moment(this.date_of_birth).format('MMM Do, YYY')
}
lifetime_string += ' - ';
if (this.date_of_death) {
lifetime_string += moment(this.date_of_death).format('MMM Do, YYY');
}
return lifetime_string
});

AuthorSchema
.virtual('date_of_birth_yyyy-mm-dd')
.get(function() {
return moment(this.date_of_birth.format('YYYY-MM-DD'));
});

AuthorSchema
.virtual('date_of_death_yyyy-mm-dd')
.get(function() {
return moment(this.date_of_death.format('YYYY-MM-DD'));
});

// Exports module
module.exports = mongoose.model('Author', AuthorSchema);
7 changes: 7 additions & 0 deletions models/bookinstance.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
var mongoose = require('mongoose');

var Schema = mongoose.Schema;
var moment = require('moment');

var BookInstanceSchema = new Schema({
book: { type: Schema.Types.ObjectId, ref: 'Book', required: true },
Expand All @@ -16,5 +17,11 @@ BookInstanceSchema
return '/catalog/bookinstance/' + this._id;
});

BookInstanceSchema
.virtual('due_back_formatted')
.get(function () {
return moment(this.due_back).format('MMMM Do, YYYY');
});

// Exports module
module.exports = mongoose.model('BookInstance', BookInstanceSchema);
19 changes: 19 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@
"body-parser": "^1.18.3",
"concurrently": "^4.1.0",
"express": "^4.16.4",
"express-validator": "^5.3.1",
"moment": "^2.24.0",
"mongoose": "^5.4.22"
},
"devDependencies": {
Expand Down
188 changes: 170 additions & 18 deletions routes/api/author.js
Original file line number Diff line number Diff line change
@@ -1,41 +1,193 @@
var Author = require('../../models/author');
var Book = require('../../models/book');
var async = require('async');

const { body, validationResult } = require('exress-validator/check');
const { sanitizeBody } = require('express-validator/filter');

// Display list of all Authors.
exports.author_list = function(req, res) {
res.send('NOT IMPLEMENTED: Author list');
exports.author_list = function(req, res, next) {
Author.find()
.sort([['family_name', 'ascending']])
.exec(function (err, list_authors) {
if (err) { return next(err); }

res.render('author_list', {title: 'Author List', author_list: list_authors });
});
};

// Display detail page for a specific Author.
exports.author_detail = function(req, res) {
res.send('NOT IMPLEMENTED: Author detail: ' + req.params.id);
exports.author_detail = function(req, res, next) {
async.parallel({
author: function(callback) {
Author.findById(req.params.id)
.exec(callback)
}, authors_books: function(callback) {
Book.find({ 'author': req.params.id },'title summary')
.exec(callback)
},
}, function(err, results) {
if (err) { return next(err); } // Error in API usage.
if (results.author==null) { // No results.
var err = new Error('Author not found');
err.status = 404;
return next(err);
}

res.render('author_detail', { title: 'Author Detail', author: results.author, author_books: results.authors_books } );
});
};

// Display Author create form on GET.
exports.author_create_get = function(req, res) {
res.send('NOT IMPLEMENTED: Author create GET');
exports.author_create_get = function(req, res, next) {
res.render('author_form', { title: 'Create Author' });
};

// Handle Author create on POST.
exports.author_create_post = function(req, res) {
res.send('NOT IMPLEMENTED: Author create POST');
};
exports.author_create_post = [
// Validate fields.
body('first_name').isLength({ min: 1 }).trim().withMessage('First name must be specified.')
.isAlphanumeric().withMessage('First name has non-alphanumeric characters.'),
body('family_name').isLength({ min: 1 }).trim().withMessage('Family name must be specified.')
.isAlphanumeric().withMessage('Family name has non-alphanumeric characters.'),
body('date_of_birth', 'Invalid date of birth').optional({ checkFalsy: true }).isISO8601(),
body('date_of_death', 'Invalid date of death').optional({ checkFalsy: true }).isISO8601(),

// Sanitize fields.
sanitizeBody('first_name').escape(),
sanitizeBody('family_name').escape(),
sanitizeBody('date_of_birth').toDate(),
sanitizeBody('date_of_death').toDate(),

// Process request after validation and sanitization.
(req, res, next) => {

// Extract the validation errors from a request.
const errors = validationResult(req);

if (!errors.isEmpty()) {
// There are errors. Render form again with sanitized values/errors messages.
res.render('author_form', { title: 'Create Author', author: req.body, errors: errors.array() });
return;
}
else {
// Data from form is valid.

// Create an Author object with escaped and trimmed data.
var author = new Author(
{
first_name: req.body.first_name,
family_name: req.body.family_name,
date_of_birth: req.body.date_of_birth,
date_of_death: req.body.date_of_death
});
author.save(function (err) {
if (err) { return next(err); }
// Successful - redirect to new author record.
res.redirect(author.url);
});
}
}
];

// Display Author delete form on GET.
exports.author_delete_get = function(req, res) {
res.send('NOT IMPLEMENTED: Author delete GET');
exports.author_delete_get = function(req, res, next) {
async.parallel({
author: function(callback) {
Author.findById(req.params.id).exec(callback)
}, authors_books: function(callback) {
Book.find({ 'author': req.params.id }).exec(callback)
},
}, function(err, results) {
if(err) {return next(err); }
if(results.author == null) {
res.redirect('/catalog/authors');
}
// Successful, so render
res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.author_books });
});
};

// Handle Author delete on POST.
exports.author_delete_post = function(req, res) {
res.send('NOT IMPLEMENTED: Author delete POST');
exports.author_delete_post = function(req, res, next) {
async.parallel({
author: function(callback) {
Author.findById(req.body.authorid).exec(callback);
}, authors_books: function(callback) {
Book.find({ 'author': req.body.authorid }).exec(callback)
},
}, function(err, results) {
if(err) { return next(err); }
// Success
if(results.author_books.length > 0) {
// Author has books. Render in same way as for GET route.
res.render('author_delete', { title: 'Delete Author', author: results.author, author_books: results.authors_books });
return;
} else {
// Author has no books. Dlete object and redirect to the list of authors.
Author.findByIdAndRemove(req.body.authorid, function deleteAuthor(err) {
if(err) { return next(err); }
// Success - go to author list
res.redirect('/catalog/authors')
})
}
});
};

// Display Author update form on GET.
exports.author_update_get = function(req, res) {
res.send('NOT IMPLEMENTED: Author update GET');
exports.author_update_get = function (req, res, next) {
Author.findById(req.params.id, function (err, author) {
if (err) { return next(err); }
if (author == null) { // No results.
var err = new Error('Author not found');
err.status = 404;
return next(err);
}
// Success.
res.render('author_form', { title: 'Update Author', author: author });
});
};

// Handle Author update on POST.
exports.author_update_post = function(req, res) {
res.send('NOT IMPLEMENTED: Author update POST');
};
exports.author_update_post = [
// Validate fields.
body('first_name').isLength({ min: 1 }).trim().withMessage('First name must be specified.')
.isAlphanumeric().withMessage('First name has non-alphanumeric characters.'),
body('family_name').isLength({ min: 1 }).trim().withMessage('Family name must be specified.')
.isAlphanumeric().withMessage('Family name has non-alphanumeric characters.'),
body('date_of_birth', 'Invalid date of birth').optional({ checkFalsy: true }).isISO8601(),
body('date_of_death', 'Invalid date of death').optional({ checkFalsy: true }).isISO8601(),

// Sanitize fields.
sanitizeBody('first_name').trim().escape(),
sanitizeBody('family_name').trim().escape(),
sanitizeBody('date_of_birth').toDate(),
sanitizeBody('date_of_death').toDate(),

// Process request after validation and sanitization.
(req, res, next) => {
// Extract the validation errors from a request.
const errors = validationResult(req);
// Create Author object with escaped and trimmed data (and the old id!)
var author = new Author({
first_name: req.body.first_name,
family_name: req.body.family_name,
date_of_birth: req.body.date_of_birth,
date_of_death: req.body.date_of_death,
_id: req.params.id
}
);
if (!errors.isEmpty()) {
// There are errors. Render the form again with sanitized values and error messages.
res.render('author_form', { title: 'Update Author', author: author, errors: errors.array() });
return;
} else {
// Data from form is valid. Update the record.
Author.findByIdAndUpdate(req.params.id, author, {}, function (err, theauthor) {
if (err) { return next(err); }
// Successful - redirect to genre detail page.
res.redirect(theauthor.url);
});
}
}
];
Loading