Finished converting post model to a cohesive model supported by mongodb. #6

This commit is contained in:
Andrew Davidson 2016-01-21 21:17:49 -05:00
parent 6288a21d33
commit cf36d983a0
10 changed files with 414 additions and 194 deletions

123
app.js
View file

@ -7,15 +7,17 @@ var async = require('async');
var multer = require('multer');
var fs = require('fs');
var markdown = require( "markdown" ).markdown;
var moment = require("moment");
// Get connected to our database
var MongoClient = require('mongodb').MongoClient
var MongoClient = require('mongodb').MongoClient;
Post = require('./post.js');
User = require('./user.js');
Category = require('./category.js');
Static = require('./static.js');
var database = require('./db.js');
var genStatic = require('./genStatic.js');
var genPhotos = require('./genPhotos.js');
var app = express();
@ -92,6 +94,7 @@ app.get('/logout', function(req, res) {
);
// Post management routing
// GET /admin/post/list
app.get('/admin/post/list/:start?',
function(req, res, next) {
var count = 25;
@ -103,9 +106,10 @@ app.get('/admin/post/list/:start?',
Post.getPosts(count, start, function(err, posts){
res.render('admin-post-list', {posts, user: req.user});
});
}
}
);
// GET /admin/post/view
app.get('/admin/post/view/:uuid?',
function (req, res, next) {
if (req.params.uuid) {
@ -125,6 +129,7 @@ app.get('/admin/post/view/:uuid?',
}
);
// GET /admin/post/new
app.get('/admin/post/new',
function(req, res, next) {
Category.getCategories(null, null, function (err, categories) {
@ -133,6 +138,7 @@ app.get('/admin/post/new',
}
);
// POST /admin/post/new
app.post('/admin/post/new',
function(req, res, next) {
var post = new Post();
@ -151,7 +157,7 @@ app.post('/admin/post/new',
Category.getByName(req.body.category, function (err, category) {
post.set("category", category.get("uuid"));
console.log(post);
//console.log(post);
post.save(function (err) {
if (!err) {
req.flash('successNotice', 'Post created.');
@ -166,6 +172,7 @@ app.post('/admin/post/new',
}
);
// GET /admin/post/edit
app.get('/admin/post/edit/:uuid',
function(req, res, next) {
Post.getByUUID(req.params.uuid, function(err, post) {
@ -190,6 +197,7 @@ app.get('/admin/post/edit/:uuid',
}
);
// POST /admin/post/edit
app.post('/admin/post/edit/:uuid',
function(req, res, next) {
Post.getByUUID(req.params.uuid, function(err, post) {
@ -215,26 +223,31 @@ app.post('/admin/post/edit/:uuid',
}
);
app.get('/admin/post/regenerate/:id?',
// GET /admin/post/regenerate
app.get('/admin/post/regenerate/:uuid?',
function(req, res, next) {
if (req.params.id) {
genStatic.generateStatic(function(err){ if (err) console.log(err) });
genPosts.generatePost(req.params.id, function(err) {
if (!err) {
req.flash('successNotice', 'Post regenerated successfully.');
res.redirect('/admin/post/view/'+req.params.id);
}
else {
req.flash('failureNotice', 'Post regeneration failed, check logs.');
res.redirect('/admin/post/view/'+req.params.id);
}
if (req.params.uuid) {
Static.updateBuildFolder(function (err) { if (err) console.log(err); });
post = Post.getByUUID(req.params.uuid, function (err, post) {
console.log("Got post: "+post.get("title"));
if (err) console.log(err);
post.generatePost(function (err) {
if (!err) {
req.flash('successNotice', 'Post regenerated successfully.');
res.redirect('/admin/post/view/'+req.params.uuid);
}
else {
console.log(err);
req.flash('failureNotice', 'Post regeneration failed, check logs.');
res.redirect('/admin/post/view/'+req.params.uuid);
}
})
});
}
else {
res.redirect('/admin/post/list');
}
}
}
);
// Photo management Routing
@ -402,60 +415,26 @@ app.get('/admin/photo/regenerate/:id',
// Admin dashboard page.
app.get('/admin',
function(req, res, next) {
async.parallel({
categories: function(callback) {
database.countCategories(function(count) {
callback(null, count);
});
},
posts: function(callback) {
Post.countPosts(function(err, count) {
callback(null, count);
});
},
galleries: function(callback) {
database.countGalleries(function(count) {
callback(null, count);
});
},
photos: function(callback) {
database.countPhotos(function(count) {
callback(null, count);
});
},
tags: function(callback) {
database.countTags(function(count) {
callback(null, count);
});
},
regenerateDate: function(callback) {
database.getLastRegenerateDate(function(date) {
var dateString = helper.epochToDateString(date.date).slice(0,-12);
callback(null, dateString);
});
},
uploadDate: function(callback) {
database.getLastUploadDate(function(date) {
var dateString = helper.epochToDateString(date.date).slice(0,-12);
callback(null, dateString);
});
}
},
function(err, results) {
res.render('admin-dashboard', {
successNotice: req.flash('successNotice'),
failureNotice: req.flash('failureNotice'),
categories: results.categories,
posts: results.posts,
galleries: results.galleries,
photos: results.photos,
tags: results.tags,
regenerateDate: results.regenerateDate,
uploadDate: results.uploadDate,
user: req.user});
}
);
}
Post.countPosts(function (err, postCount) {
Category.countCategories(function (err, categoryCount) {
Post.getLastGenerateDate(function (err, lastGenerateDate) {
Post.getLastUploadDate( function (err, lastUploadDate) {
res.render('admin-dashboard', {
successNotice: req.flash('successNotice'),
failureNotice: req.flash('failureNotice'),
categories: categoryCount,
posts: postCount,
galleries: "###",
photos: "###",
tags: "###",
regenerateDate: moment(lastGenerateDate).format("YYYY-MM-DD HH:mm"),
uploadDate: moment(lastUploadDate).format("YYYY-MM-DD HH:mm"),
user: req.user});
})
})
})
})
}
);
// Routes for previewing sent versions of the built items.

96
category.js Normal file
View file

@ -0,0 +1,96 @@
var moment = require("moment");
var _ = require("lodash");
var schemas = require('./schemas.js');
var Category = function(data) {
this.data = data;
}
Category.prototype.data = {}
Category.prototype.get = function (name) {
return this.data[name];
}
Category.prototype.set = function (name, value) {
this.data[name] = value;
}
Category.prototype.sanitize = function (data) {
data = data || {};
schema = schemas.category;
return _.pick(_.defaults(data, schema), _.keys(schema));
}
Category.prototype.save = function (callback) {
var self = this;
this.data = this.sanitize(this.data);
db.collection("categories").save(this.data, callback(err));
}
// Function to get a YYYY-MM-DD format of the current CategoryDate
// Requires no arguments.
Category.prototype.getShortDate = function () {
return moment(this.get("createdDate")).format("YYYY-MM-DD");
}
// Get a specific category by its name
// Returns a Category object
Category.getByName = function (name, callback) {
db.collection("categories").findOne({name: name}, function(err, doc) {
callback(err, new Category(doc));
});
}
// Get a specific category by its UUID
// Returns a Category object
Category.getByUUID = function (uuid, callback) {
db.collection("categories").findOne({uuid: uuid}, function(err, doc) {
callback(err, new Category(doc));
});
}
// Function to get a count of current Categories
// Returns count of Categories
Category.countCategories = function(callback) {
db.collection("categories").find({}).count(function (err, count) {
if (err) console.log(err);
callback(err, count);
});
}
// Function to get a list of all existing categories
// Start and count are optional, but if start is used, count must be used as well.
// Returns an array of Category objects
Category.getCategories = function(count, start, callback) {
if (typeof callback === undefined) {
if (typeof start === undefined) {
callback = count;
count = undefined;
start = undefined;
}
else {
callback = start;
start = undefined;
}
}
var options = {}
if (start) options.skip=start;
if (count) options.limit=count;
options.sort = {name: 1};
db.collection("categories").find({}, options).toArray(function(err, docs) {
if (err) console.log(err);
var rows = [];
for (i=0; i<docs.length; i++) {
rows.push(new Category(docs[i]));
}
callback(null, rows);
});
}
module.exports = Category;

BIN
dump/crunch/categories.bson Normal file

Binary file not shown.

View file

@ -0,0 +1 @@
{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"crunch.categories"}]}

View file

@ -1,110 +0,0 @@
var fs = require('fs');
var mkdirp = require('mkdirp');
var jade = require('jade');
var markdown = require( "markdown" ).markdown;
var async = require('async');
var db = require('./db.js');
// Generate all archive pages associated with a particular post ID
// This currently calls the functions to make the yearly and monthly archives.
var generatePostArchives = function (id, cb) {
db.getPostById(id, function(post) {
console.log('Generating dependencies for Post: '+id);
var date = new Date(post.postDate);
var year = date.getFullYear();
console.log('Generating archive for year: '+year);
var month = date.getMonth()+1;
async.parallel({
monthArchiveErr: function(callback) {
generatePostMonthArchive(year, function(err) {
callback(null, err);
});
},
yearArchiveErr: function(callback) {
generatePostYearArchive(month, function(err) {
callback(null, err);
});
}
}, function(err, results) {
if (!yearArchiveErr && !monthArchiveErr) {
cb(null);
}
else if (yearArchiveErr) {
cb(yearArchiveErr);
}
else if (monthArchiveErr) {
cb(monthArchiveErr);
}
else {
cb(null);
}
});
});
}
exports.generatePostArchives = generatePostArchives;
// Function that generates the yearly archive pages.
// TODO: make this not a skeleton.
// TODO: create pagination.
var generatePostYearArchive = function (year, cb) {
console.log('Generating archive for year: '+year);
cb(null);
}
exports.generatePostYearArchive = generatePostYearArchive;
// Function that generates the monthly archive pages.
// TODO: Make this not a skeleton.
// TODO: create pagination.
var generatePostMonthArchive = function (month, cb) {
console.log('Generating archive for month: '+month);
cb(null);
}
exports.generatePostMonthArchive = generatePostMonthArchive;
// Function to generate a single post page.
// TODO: (re)generate images as required.
var generatePost = function (id, cb) {
db.getPostById(id, function(post) {
console.log('Fetching post id: '+id);
var title = post.title;
var content = markdown.toHTML(post.markdown);
var slug = post.slug;
var postDate = helper.epochToShortDateString(post.postDate);
var url = '/blog/'+postDate.slice(0,4)+'/'+postDate.slice(5,7)+'/'+slug+'/';
var filepath = 'generated'+url;
var filename = filepath+'index.html';
var options = {
pretty: false,
title: title,
content: content,
slug: slug,
url: url,
postDate: postDate
};
console.log('Rendering post: '+title);
var jadeOut = jade.renderFile('views/render-post.jade', options);
console.log('Creating directory: '+filepath);
mkdirp(filepath, function(err) {
if (err) {
console.log(err);
}
else {
console.log('Writing to file: '+filename);
fs.writeFile(filename, jadeOut, 'utf-8', function(err) {
if (err) console.log(err);
cb(err);
});
}
});
});
}
exports.generatePost = generatePost;

244
post.js Normal file
View file

@ -0,0 +1,244 @@
var moment = require("moment");
var _ = require("lodash");
var fs = require('fs');
var mkdirp = require('mkdirp');
var jade = require('jade');
var markdown = require( "markdown" ).markdown;
var path = require('path');
var schemas = require('./schemas.js');
var config = require("./config.js").config;
var Post = function(data) {
this.data = this.sanitize(data);
}
////////////////////////////////////////////////////////////////////////////////////////
// Prototype Setup
////////////////////////////////////////////////////////////////////////////////////////
Post.prototype.data = {}
Post.prototype.get = function (name) {
return this.data[name];
}
Post.prototype.set = function (name, value) {
this.data[name] = value;
}
Post.prototype.sanitize = function (data) {
data = data || {};
schema = schemas.post;
return _.pick(_.defaults(data, schema), _.keys(schema));
}
Post.prototype.save = function (callback) {
var self = this;
this.set("updatedDate", new Date());
this.data = this.sanitize(this.data);
db.collection("posts").update({uuid: this.get("uuid")}, this.data, {upsert: true}, function(err) {
callback(err);
});
}
////////////////////////////////////////////////////////////////////////////////////////
// Prototype Functions
////////////////////////////////////////////////////////////////////////////////////////
// Parse a string of tags
// Returns a list of tags
Post.prototype.tagPost = function(str) {
this.set("tags", []);
// we don't need no stinking commas
var str = str.replace(/,/g,'');
// remove accents, swap ñ for n, etc
var from = "àáäâèéëêìíïîòóöôùúüûñç·/,:;&%";
var to = "aaaaeeeeiiiioooouuuunc-------";
for (var i=0, l=from.length ; i<l ; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
// Make into a sorted, unique array
var l = str.split(" ");
var list = []
for (i = 0; i < l.length; i ++) {
if (l[i]) {
list[i] = l[i].toLowerCase().trim()
}
}
list = list.sort();
var unique = list.filter(function(elem, index, self) {
return index == self.indexOf(elem);
})
// Push these into the post.
for (var i = 0, size = unique.length; i < size; i++) {
if (unique[i]) {
this.data.tags.push({name: list[i]});
}
}
}
// Function to get a YYYY-MM-DD format of the current postDate
// Arguments: None
// Returns: Date as String
Post.prototype.getShortDate = function () {
return moment(this.get("postDate")).format("YYYY-MM-DD");
}
// Function to return rendered markdown of a post
// Arguments: None
// Returns: Content as String
Post.prototype.renderMarkdown = function () {
return markdown.toHTML(this.get("markdown"));
}
// Function to get the relative url of a post, this sets the permalink structure
// Arguments: None
// Returns: URL as String
Post.prototype.getURL = function () {
var url = "/blog/";
url += moment(this.get("postDate")).format("YYYY/MM/");
url += this.get("slug") + '/';
return url;
}
// Function to return the path of the post file
// Arguments: None
// Returns: Path as a string
Post.prototype.getFilePath = function () {
var file = path.join(this.getDirectory(),'index.html');
return file;
}
// Function to return the post build directory
// Arguments: None
// Returns: Directory path as String
Post.prototype.getDirectory = function () {
var dir = path.join(config.genDir,this.getURL());
return dir;
}
// Build a slug from a title string
// Arguments: None
// Returns: Post Slug as String
Post.prototype.makeSlug = function () {
str = this.get("title").replace(/^\s+|\s+$/g, ''); // trim
str = str.toLowerCase();
str = str.trim();
// remove accents, swap ñ for n, etc
var from = "àáäâèéëêìíïîòóöôùúüûñç·/_,:;&";
var to = "aaaaeeeeiiiioooouuuunc-------";
for (var i=0, l=from.length ; i<l ; i++) {
str = str.replace(new RegExp(from.charAt(i), 'g'), to.charAt(i));
}
str = str.replace(/[^a-z0-9 -]/g, '') // remove invalid chars
.replace(/\s+/g, '-') // collapse whitespace and replace by -
.replace(/-+/g, '-'); // collapse dashes
return str;
};
// Function to generate static renderings of a particular post.
// Arguments: Callback function
// Returns: err
Post.prototype.generatePost = function (callback) {
var options = {
pretty: false,
post: this
};
console.log('Rendering post: '+this.get("title"));
var jadeOut = jade.renderFile('views/render-post.jade', options);
console.log('Creating directory: '+ this.getDirectory());
mkdirp.sync(this.getDirectory());
fs.writeFile(this.getFilePath(), jadeOut, 'utf-8', function (err) {
callback(err);
});
}
////////////////////////////////////////////////////////////////////////////////////////
// Independent Functions
////////////////////////////////////////////////////////////////////////////////////////
// Function to find the most recent post generation.
// Returns a date object
Post.getLastGenerateDate = function (callback) {
db.collection("posts").findOne({}, {lastGenerateDate: 1}, {sort: {lastGenerateDate: -1}},
function(err, doc) {
callback(err, doc.lastGenerateDate);
}
)
};
// Function to find the most recent post upload date.
// Returns a date object
Post.getLastUploadDate = function (callback) {
db.collection("posts").findOne({}, {lastUploadDate: 1}, {sort: {lastUploadDate: -1}},
function (err, doc) {
callback(err, doc.lastUploadDate);
}
)
};
// Get a specific post by it's UUID
// Returns post object
Post.getByUUID = function (uuid, callback) {
db.collection("posts").findOne({uuid: uuid}, function(err, doc) {
callback(err, new Post(doc));
});
}
// Function to get a count of current posts
// Returns count of posts
Post.countPosts = function(callback) {
db.collection("posts").find({}).count(function (err, count) {
if (err) console.log(err);
callback(err, count);
});
}
// Function to get a list of posts of a certain count and starting at an offset
// Count and start are optional, when start is specified count must be specified.
// Returns a list of post objects
Post.getPosts = function(count, start, callback) {
if (typeof callback === undefined) {
if (typeof start === undefined) {
callback = count;
count = undefined;
start = undefined;
}
else {
callback = start;
start = undefined;
}
}
var options = {}
if (start) options.skip=start;
if (count) options.limit=count;
options.sort = [['postDate', 'desc'],['title', 'asc']];
db.collection("posts").find({},options).toArray( function(err, docs) {
console.log(err);
posts = [];
for (i=0; i<docs.length; i++) {
posts.push(new Post(docs[i]));
}
callback(null, posts);
});
}
// Export the Post object for external use.
module.exports = Post;

View file

@ -12,7 +12,6 @@ schemas = {
uuid: uuid.v1(),
title: null,
slug: null,
url: null,
markdown: null,
createdDate: new Date(),
postDate: new Date(),
@ -21,8 +20,8 @@ schemas = {
published: false,
category: null,
tags: [],
lastGenerateDate: 0,
lastUpload: 0
lastGenerateDate: new Date("Mon Jan 1 1900 00:00:00 GMT-0500"),
lastUploadDate: new Date("Mon Jan 1 1900 00:00:00 GMT-0500")
},
category: {
uuid: uuid.v1(),

View file

@ -2,17 +2,29 @@ var async = require('async');
var fs = require('fs');
var ncp = require('ncp').ncp;
var mkdirp = require('mkdirp');
var config = require('./config.js').config;
var Static = function(data) {
this.data = data;
}
Post.prototype.data = {}
Post.prototype.get = function (name) {
return this.data[name];
}
Post.prototype.set = function (name, value) {
this.data[name] = value;
}
Static.updateBuildFolder = function (callback) {
fs.unlink(destFolder, function(err) {
console.log(destFolder+' deleted.');
ncp(sourceFolder, destFolder, function(err) {
console.log("Removing "+config.staticDest);
fs.unlink(config.staticDest, function(err) {
console.log(config.staticDest+' deleted.');
ncp(config.staticSource, config.staticDest, function(err) {
if (!err) {
console.log(sourceFolder+' copied to '+destFolder);
console.log(config.staticSource+' copied to '+config.staticDest);
callback(null);
}
else {
@ -22,5 +34,4 @@ Static.updateBuildFolder = function (callback) {
});
}
module.exports = Static;

View file

@ -9,7 +9,7 @@ block content
include ./admin-messages.jade
form(method="post", action="/admin/post/edit/#{post.id}")
form(method="post", action="/admin/post/edit/#{post.get('uuid')}")
div(class="row page-header")
div(class="input-group input-group-lg col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
input(class="form-control", type="text", name="title", placeholder="Enter title", value="#{post.get('title')}")

View file

@ -1,6 +1,6 @@
extends render-layout
block content
h3: a(href="#{url}") #{title}
p #{postDate}
div !{content}
h3: a(href="#{post.getURL()}") #{post.get("title")}
p #{post.getShortDate()}
div #{post.renderMarkdown()}