crunch-node/post.js

483 lines
15 KiB
JavaScript
Raw Permalink Normal View History

var moment = require("moment");
var _ = require("lodash");
var fs = require('fs');
var mkdirp = require('mkdirp');
var jade = require('jade');
var marked = require('marked');
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.toString = function () {
return 'Post: ' + this.get('title') + ' - ' + this.getShortDate();
}
Post.prototype.sanitize = function (data) {
data = data || {};
schema = schemas.post;
return _.pick(_.defaults(data, schema), _.keys(schema));
}
Post.prototype.save = function (date, callback) {
var self = this;
if (date) {
this.set("updatedDate", date);
} else {
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 marked(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.buildDir,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 build static renderings of a particular post.
// Arguments: Callback function
// Returns: err
Post.prototype.build = function (callback) {
console.log('Rendering ' + this);
year = moment(this.get("postDate")).format("YYYY");
month = moment(this.get("postDate")).format("MM");
Post.buildYearIndex(year, function (err) {
if (err) console.log(err);
});
Post.buildMonthIndex(year, month, function (err) {
if (err) console.log(err);
});
Post.buildIndex();
var options = {
pretty: true,
post: this
};
var jadeOut = jade.renderFile('views/render-post.jade', options);
self = this;
mkdirp(self.getDirectory(), function (err) {
if (!err) {
fs.writeFile(self.getFilePath(), jadeOut, 'utf-8', function (err) {
if (!err) {
self.set('lastBuildDate', new Date());
self.save(self.get('lastBuildDate'), function (err) {
callback(err);
});
} else {
callback(err);
}
});
} else {
callback(err);
}
});
}
////////////////////////////////////////////////////////////////////////////////////////
// Independent Functions
////////////////////////////////////////////////////////////////////////////////////////
// Function to find the most recent post generation.
// Returns a date object
Post.getLastBuildDate = function (callback) {
db.collection("posts").findOne({}, {lastBuildDate: 1}, {sort: {lastBuildDate: -1}},
function(err, doc) {
callback(err, doc.lastBuildDate);
}
)
};
// Function to find the most recent post upload date.
// Returns: err, 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: err, post object
Post.getByUUID = function (uuid, callback) {
db.collection("posts").findOne({uuid: uuid}, function(err, doc) {
post = new Post(doc);
console.log('Retrieved ' + post);
callback(err, post);
});
}
// Function to get a count of current posts
// Returns: err, 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: err, 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) {
if (!err) {
posts = [];
for (i=0; i<docs.length; i++) {
posts.push(new Post(docs[i]));
}
callback(null, posts);
} else {
console.log(err);
callback(err, null);
}
});
}
// Function to find posts that need to be built
// Inputs: Callback function
// Returns: err, list of Post objects
2016-01-23 23:19:37 +00:00
Post.getNeedsBuild = function (callback) {
db.collection("posts").find({
$where: "(this.lastBuildDate < this.updatedDate) && this.published"
}).toArray(
function (err, docs) {
if (err) console.log(err);
posts = [];
for (i=0; i<docs.length; i++) {
posts.push(new Post(docs[i]));
}
callback(null, posts);
}
);
}
// Function to find posts that need to be uploaded
// Inputs: Callback function
// Returns: err, List of Post objects
Post.getNeedsUpload = function (callback) {
db.collection("posts").find({
$where: '(this.lastUploadDate < this.updatedDate) && \
(this.lastBuildDate >= this.updatedDate) && \
this.published'
}).toArray( function (err, docs) {
if (err) console.log(err);
posts = [];
if (docs) {
for (i=0; i<docs.length; i++) {
posts.push(new Post(docs[i]));
}
callback(null, posts);
} else {
callback("No posts require upload.", null);
}
}
);
}
// Function to build an index for a specific month
// Inputs: Year, Month, callback
// Returns: err
Post.buildMonthIndex = function (year, month, callback) {
console.log('Rendering month index: '+year+'/'+month);
var directory = config.buildDir
directory += "/blog/";
directory += year + '/' + month + '/';
console.log('Finding posts from '+year+'/'+month);
mkdirp(directory, function (err) {
if (!err) {
db.collection('posts').find({
$where: '(this.postDate >= new Date('+year+', '+month+'-1, 1, 0, 0, 0)) \
&& (this.postDate <= new Date('+year+', '+month+', 0, 23, 59, 59)) \
&& this.published'
}, {sort: [['postDate', 'desc'],['title', 'asc']]}
).toArray(function (err, docs) {
if (!err) {
console.log('Found '+docs.length+' posts from '+year+'/'+month);
posts = []
for (i=0; i<docs.length; i++) {
posts.push(new Post(docs[i]));
}
var jadeOut = jade.renderFile('views/render-post-index.jade', {
pretty: true,
posts: posts,
pageTitle: 'Posts from '+year+'/'+month,
title: 'Posts from '+year+'/'+month
});
file = path.join(directory, 'index.html');
fs.writeFile(file, jadeOut, 'utf-8', function (err) {
callback(err);
});
} else {
callback(err)
}
});
} else {
callback(err);
}
});
}
// Function to build an index for a specific year
// Inputs: Year, callback
// Returns: err
Post.buildYearIndex = function (year, callback) {
console.log('Rendering year index: '+year);
var directory = config.buildDir
directory += "/blog/";
directory += year + '/';
console.log('Finding posts from '+year);
mkdirp(directory, function (err) {
if (!err) {
db.collection('posts').find({
$where: '(this.postDate >= new Date('+year+', 0, 1, 0, 0, 0)) \
&& (this.postDate <= new Date('+year+'+1, 0, 0, 23, 59, 59)) \
&& this.published'
}, {sort: [['postDate', 'desc'],['title', 'asc']]}
).toArray(function (err, docs) {
if (!err) {
console.log('Found '+docs.length+' posts from '+year);
posts = []
for (i=0; i<docs.length; i++) {
posts.push(new Post(docs[i]));
}
var jadeOut = jade.renderFile('views/render-post-index.jade', {
pretty: true,
posts: posts,
pageTitle: 'Posts from '+year,
title: 'Posts from '+year
});
file = path.join(directory, 'index.html');
console.log(file);
fs.writeFile(file, jadeOut, 'utf-8', function (err) {
callback(err);
});
} else {
callback(err)
}
});
} else {
callback(err);
}
});
}
// Function to build the main index
// Inputs: none
// Returns: none
Post.buildIndex = function () {
console.log("Building home index");
Post.getPosts(null, null, function (err, posts) {
pages = posts.length / 10;
for ( p = 0; p < pages ; p++) {
(function (p) {
console.log("Building page "+p+" of index.");
var start = p*10;
var end = (p+1)*10-1;
if ( p > 0 ) {
var title = 'Page 2';
var pageTitle = 'Page 2';
}
if ( p === 0 ) {
var prev = -1;
} else {
var prev = p-1;
}
if ( p + 1 < pages ) {
var next = p+2;
} else {
var next = false;
}
var jadeOut = jade.renderFile('views/render-post-index.jade', {
pretty: true,
title: title,
pageTitle: pageTitle,
next: next,
prev: prev,
posts: posts.slice(start, end)
});
if (p === 0) {
var directory = path.join(config.buildDir, 'blog');
var file = path.join(directory, 'index.html');
} else {
var directory = path.join(config.buildDir, 'blog', (p+1).toString());
var file = path.join(directory, 'index.html');
}
mkdirp(directory, function (err) {
console.log('Made directory: '+directory);
if (!err) {
fs.writeFile(file, jadeOut, 'utf-8', function (err) {
console.log('Wrote out: '+file);
if (err) console.log(err);
});
} else {
console.log('Failed to build '+file, err)
}
});
})(p);
}
});
}
// Export the Post object for external use.
module.exports = Post;