Initial commit, post creation and build is just barely working.
This commit is contained in:
commit
4ecb1a9185
24 changed files with 1402 additions and 0 deletions
13
.gitignore
vendored
Normal file
13
.gitignore
vendored
Normal file
|
@ -0,0 +1,13 @@
|
|||
.DS_Store
|
||||
config.js
|
||||
|
||||
logs
|
||||
*.log
|
||||
npm-debug.log*
|
||||
|
||||
pids
|
||||
*.pid
|
||||
|
||||
node_modules
|
||||
|
||||
preload.sql
|
312
app.js
Normal file
312
app.js
Normal file
|
@ -0,0 +1,312 @@
|
|||
var express = require('express');
|
||||
var flash = require('express-flash');
|
||||
var path = require('path');
|
||||
var passport = require('passport');
|
||||
var Strategy = require('passport-local').Strategy;
|
||||
var async = require('async');
|
||||
|
||||
var helper = require('./helper.js');
|
||||
var db = require('./db.js');
|
||||
var build = require('./build.js');
|
||||
|
||||
var app = express();
|
||||
|
||||
// view engine setup
|
||||
app.set('views', path.join(__dirname, 'views'));
|
||||
app.set('view engine', 'jade');
|
||||
|
||||
// Make HTML pretty while we're setting up our jade templates
|
||||
// but it doesn't need to be pretty in production.
|
||||
if (app.get('env') === 'development') {
|
||||
app.locals.pretty = true;
|
||||
};
|
||||
|
||||
|
||||
// uncomment after placing your favicon in /public
|
||||
//app.use(require('serve-favicon')(path.join(__dirname, 'public',
|
||||
// 'favicon.ico')));
|
||||
app.use(flash());
|
||||
app.use(require('morgan')('dev'));
|
||||
app.use(require('cookie-parser')());
|
||||
app.use(require('body-parser').urlencoded({ extended: true }));
|
||||
app.use(require('express-session')({ secret: 'amdasdfasdfamd',
|
||||
resave: true, saveUninitialized: true }));
|
||||
app.use(express.static(path.join(__dirname, 'public')));
|
||||
|
||||
// Get config variables
|
||||
var config = require('./config');
|
||||
|
||||
// Setup authentication via twitter.
|
||||
passport.use(new Strategy(
|
||||
function(username, password, done) {
|
||||
db.getUser(username, password, function(user) {
|
||||
if (!user) { return done(null, false); }
|
||||
return done(null, user);
|
||||
});
|
||||
}
|
||||
));
|
||||
|
||||
passport.serializeUser(function(user, done) {
|
||||
done(null, user.id);
|
||||
});
|
||||
|
||||
passport.deserializeUser(function(id, done) {
|
||||
db.getUserById(id, function (user) {
|
||||
if (!user) { return done(false); }
|
||||
done(null, user);
|
||||
});
|
||||
});
|
||||
|
||||
app.use(passport.initialize());
|
||||
app.use(passport.session());
|
||||
|
||||
app.get('/login', function(req, res) {
|
||||
res.render('admin-login', {user: req.user});
|
||||
});
|
||||
|
||||
app.post('/login',
|
||||
passport.authenticate('local', { successReturnToOrRedirect: '/admin', failureRedirect: '/login' })
|
||||
);
|
||||
|
||||
app.get('/logout', function(req, res) {
|
||||
req.logout();
|
||||
res.redirect('/');
|
||||
});
|
||||
|
||||
app.get('/admin/post/list/:start?',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
var count = 25;
|
||||
if (req.params.start) {
|
||||
var start = req.params.start;
|
||||
} else {
|
||||
var start = 0;
|
||||
}
|
||||
db.listPosts(count, start, function(posts){
|
||||
for (post in posts) {
|
||||
var date = new Date(posts[post].postDate);
|
||||
posts[post].dateString = date.getFullYear() + '-' +
|
||||
("0" + (date.getMonth()+1)).slice(-2) + '-' +
|
||||
("0" + date.getDate()).slice(-2);
|
||||
}
|
||||
res.render('admin-post-list', {posts, user: req.user});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/admin/photo/new',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
res.render('admin-photo-new', { user: req.user });
|
||||
});
|
||||
|
||||
app.get('/admin/post/new',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
db.listCategories(function(rows){
|
||||
res.render('admin-post-new', { categories: rows, user: req.user });
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/admin/post/new',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
var title = req.body.title;
|
||||
if (req.body.slug != "") {
|
||||
slug = req.body.slug;
|
||||
} else {
|
||||
slug = helper.makeSlug(title);
|
||||
}
|
||||
var markdown = req.body.markdown;
|
||||
var postDate = helper.dateToEpoch(new Date(req.body.postDate));
|
||||
var updatedDate = postDate;
|
||||
var createDate = helper.dateToEpoch(new Date());
|
||||
var categoryName = req.body.category;
|
||||
|
||||
db.createPost(title, slug, markdown, postDate, updatedDate, createDate, function(err, row) {
|
||||
db.setPostCategory(row.id, categoryName, function(category) {
|
||||
req.flash('successNotice', 'Post created.');
|
||||
res.redirect('/admin/post/edit/'+row.id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/admin/post/edit/:id',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
var id = req.params.id;
|
||||
async.parallel({
|
||||
categories: function(callback) {
|
||||
db.listCategories(function(categories) {
|
||||
callback(null, categories);
|
||||
})
|
||||
},
|
||||
postCategory: function(callback) {
|
||||
db.getPostCategory(id, function(category) {
|
||||
callback(null, category);
|
||||
})
|
||||
},
|
||||
postTags: function(callback) {
|
||||
db.getPostTagsAsString(id, function(tagString) {
|
||||
callback(null, tagString);
|
||||
})
|
||||
},
|
||||
post: function(callback) {
|
||||
db.getPostById(id, function(post) {
|
||||
callback(null, post);
|
||||
})
|
||||
}
|
||||
},
|
||||
function(err, results) {
|
||||
res.render('admin-post-edit', {
|
||||
successNotice: req.flash('successNotice'),
|
||||
failureNotice: req.flash('failureNotice'),
|
||||
categories: results.categories,
|
||||
postCategory: results.postCategory,
|
||||
post: results.post,
|
||||
postTags: results.postTags,
|
||||
formattedDate: helper.epochToDateString(results.post.postDate),
|
||||
user: req.user
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.post('/admin/post/edit/:id',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
var id = req.params.id;
|
||||
var title = req.body.title;
|
||||
var slug = helper.makeSlug(title);
|
||||
var markdown = req.body.markdown;
|
||||
var postDate = helper.dateToEpoch(new Date(req.body.postDate));
|
||||
var categoryName = req.body.category;
|
||||
var tags = helper.parseTags(req.body.tags);
|
||||
db.tagPost(id, tags);
|
||||
db.updatePost(id, title, slug, markdown, postDate, function(err, row) {
|
||||
db.setPostCategory(id, categoryName, function(err) {
|
||||
req.flash('successNotice', 'Post updated.');
|
||||
res.redirect('/admin/post/edit/'+id);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
app.get('/admin/post/rebuild/:id?',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
if (req.params.id) {
|
||||
build.buildPost(req.params.id, function(err) {
|
||||
if (!err) {
|
||||
req.flash('successNotice', 'Post rebuilt successfully.');
|
||||
res.redirect('/admin/post/edit/'+req.params.id);
|
||||
}
|
||||
else {
|
||||
req.flash('failureNotice', 'Post rebuild failed, check logs.');
|
||||
res.redirect('/admin/post/edit/'+req.params.id);
|
||||
}
|
||||
});
|
||||
}
|
||||
else {
|
||||
res.redirect('/admin/post/list');
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
app.get('/admin',
|
||||
require('connect-ensure-login').ensureLoggedIn(),
|
||||
function(req, res, next) {
|
||||
async.parallel({
|
||||
categories: function(callback) {
|
||||
db.countCategories(function(count) {
|
||||
callback(null, count);
|
||||
});
|
||||
},
|
||||
posts: function(callback) {
|
||||
db.countPosts(function(count) {
|
||||
callback(null, count);
|
||||
});
|
||||
},
|
||||
galleries: function(callback) {
|
||||
db.countGalleries(function(count) {
|
||||
callback(null, count);
|
||||
});
|
||||
},
|
||||
photos: function(callback) {
|
||||
db.countPhotos(function(count) {
|
||||
callback(null, count);
|
||||
});
|
||||
},
|
||||
tags: function(callback) {
|
||||
db.countTags(function(count) {
|
||||
callback(null, count);
|
||||
});
|
||||
},
|
||||
rebuildDate: function(callback) {
|
||||
db.getLastRebuildDate(function(date) {
|
||||
var dateString = helper.epochToDateString(date.date).slice(0,-14);
|
||||
callback(null, dateString);
|
||||
});
|
||||
},
|
||||
uploadDate: function(callback) {
|
||||
db.getLastUploadDate(function(date) {
|
||||
var dateString = helper.epochToDateString(date.date).slice(0,-14);
|
||||
callback(null, dateString);
|
||||
});
|
||||
}
|
||||
},
|
||||
function(err, results) {
|
||||
res.render('admin-dashboard', {
|
||||
categories: results.categories,
|
||||
posts: results.posts,
|
||||
galleries: results.galleries,
|
||||
photos: results.photos,
|
||||
tags: results.tags,
|
||||
rebuildDate: results.rebuildDate,
|
||||
uploadDate: results.uploadDate,
|
||||
user: req.user});
|
||||
}
|
||||
);
|
||||
});
|
||||
|
||||
app.get('/', function(req, res, next) {
|
||||
res.render('admin-index', { title: 'AMDavidson.com', user: req.user });
|
||||
});
|
||||
|
||||
// catch 404 and forward to error handler
|
||||
app.use(function(req, res, next) {
|
||||
var err = new Error('Not Found');
|
||||
err.status = 404;
|
||||
next(err);
|
||||
});
|
||||
|
||||
// error handlers
|
||||
|
||||
// development error handler
|
||||
// will print stacktrace
|
||||
if (app.get('env') === 'development') {
|
||||
app.use(function(err, req, res, next) {
|
||||
res.status(err.status || 500);
|
||||
res.render('admin-error', {
|
||||
message: err.message,
|
||||
error: err,
|
||||
user: req.user
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// production error handler
|
||||
// no stacktraces leaked to user
|
||||
app.use(function(err, req, res, next) {
|
||||
res.status(err.status || 500);
|
||||
res.render('error', {
|
||||
message: err.message,
|
||||
user: req.user,
|
||||
error: {}
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
app.set('port', process.env.PORT || 3000)
|
||||
var server = require('http').createServer(app)
|
||||
|
||||
server.listen(app.get('port'), function() {
|
||||
console.log("Server listening on port " + app.get('port'));
|
||||
});
|
113
build.js
Normal file
113
build.js
Normal file
|
@ -0,0 +1,113 @@
|
|||
var fs = require('fs');
|
||||
var mkdirp = require('mkdirp');
|
||||
var jade = require('jade');
|
||||
var markdown = require( "markdown" ).markdown;
|
||||
var async = require('async');
|
||||
|
||||
var helper = require('./helper.js');
|
||||
var db = require('./db.js');
|
||||
|
||||
// Build all archive pages associated with a particular post ID
|
||||
// This currently calls the functions to make the yearly and monthly archives.
|
||||
var buildPostArchives = function (id, cb) {
|
||||
db.getPostById(id, function(post) {
|
||||
console.log('Building dependencies for Post: '+id);
|
||||
var date = new Date(post.postDate);
|
||||
var year = date.getFullYear();
|
||||
console.log('Building archive for year: '+year);
|
||||
var month = date.getMonth()+1;
|
||||
async.parallel({
|
||||
monthArchiveErr: function(callback) {
|
||||
buildPostMonthArchive(year, function(err) {
|
||||
callback(null, err);
|
||||
});
|
||||
},
|
||||
yearArchiveErr: function(callback) {
|
||||
buildPostYearArchive(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.buildPostArchives = buildPostArchives;
|
||||
|
||||
|
||||
// Function that builds the yearly archive pages.
|
||||
// TODO: make this not a skeleton.
|
||||
// TODO: create pagination.
|
||||
var buildPostYearArchive = function (year, cb) {
|
||||
console.log('Building archive for year: '+year);
|
||||
cb(null);
|
||||
}
|
||||
exports.buildPostYearArchive = buildPostYearArchive;
|
||||
|
||||
|
||||
// Function that builds the monthly archive pages.
|
||||
// TODO: Make this not a skeleton.
|
||||
// TODO: create pagination.
|
||||
var buildPostMonthArchive = function (month, cb) {
|
||||
console.log('Building archive for month: '+month);
|
||||
cb(null);
|
||||
}
|
||||
exports.buildPostMonthArchive = buildPostMonthArchive;
|
||||
|
||||
|
||||
// Function to build a single post page.
|
||||
// TODO: (re)build images as required.
|
||||
var buildPost = 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 = 'build'+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);
|
||||
|
||||
//console.log(jadeOut);
|
||||
|
||||
fs.writeFile(filename, jadeOut, 'utf-8', function(err) {
|
||||
if (err) console.log(err);
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
exports.buildPost = buildPost;
|
1
build/.gitignore
vendored
Normal file
1
build/.gitignore
vendored
Normal file
|
@ -0,0 +1 @@
|
|||
*.*
|
BIN
data/logo.png
Normal file
BIN
data/logo.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 96 KiB |
353
db.js
Normal file
353
db.js
Normal file
|
@ -0,0 +1,353 @@
|
|||
var sqlite = require('sqlite3').verbose();
|
||||
var db = new sqlite.Database('./app.db');
|
||||
var helper = require('./helper.js');
|
||||
var async = require('async');
|
||||
|
||||
// Function to get a user record by the username and password
|
||||
// Returns SQL row for that user
|
||||
exports.getUser = function(username, password, cb) {
|
||||
db.get('SELECT salt FROM users WHERE username = ?', username, function(err, row) {
|
||||
var hash = helper.hashPassword(password, row.salt);
|
||||
db.get('SELECT username, id, displayName, createDate \
|
||||
FROM users WHERE username = ? AND password = ?',
|
||||
username, hash, function(err, row) {
|
||||
cb(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a user record by id, does not validate user password
|
||||
// Returns SQL row for that user
|
||||
exports.getUserById = function(id, cb) {
|
||||
db.get('SELECT username, id, displayName, createDate \
|
||||
FROM users WHERE id = ?', id, function(err, row) {
|
||||
cb(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get the latest rebuild date
|
||||
// Returns a epoch time in seconds
|
||||
exports.getLastRebuildDate = function(cb) {
|
||||
db.get('SELECT MAX(lastBuildDate) as date FROM ( \
|
||||
SELECT lastBuildDate FROM posts \
|
||||
UNION \
|
||||
SELECT lastBuildDate FROM photos \
|
||||
UNION \
|
||||
SELECT lastBuildDate FROM galleries);',
|
||||
function(err, row) {
|
||||
cb(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get the latest upload date
|
||||
// Returns a epoch time in seconds
|
||||
exports.getLastUploadDate = function(cb) {
|
||||
db.get('SELECT MAX(lastUpload) as date FROM ( \
|
||||
SELECT lastUpload FROM posts \
|
||||
UNION \
|
||||
SELECT lastUpload FROM photos \
|
||||
UNION \
|
||||
SELECT lastUpload FROM galleries);',
|
||||
function(err, row) {
|
||||
cb(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a count of current posts
|
||||
// Returns count of posts
|
||||
exports.countPosts = function(cb) {
|
||||
db.get('SELECT count(id) as count FROM posts;', function(err, count) {
|
||||
cb(count.count);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a count of current galleries
|
||||
// Returns count of galleries
|
||||
exports.countGalleries = function(cb) {
|
||||
db.get('SELECT count(id) as count FROM galleries;', function(err, count) {
|
||||
cb(count.count);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a count of current categories
|
||||
// Returns count of categories
|
||||
exports.countCategories = function(cb) {
|
||||
db.get('SELECT count(id) as count FROM categories;', function(err, count) {
|
||||
cb(count.count);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a count of current photos
|
||||
// Returns count of tags
|
||||
exports.countPhotos = function(cb) {
|
||||
db.get('SELECT count(id) as count FROM photos;', function(err, count) {
|
||||
cb(count.count);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a count of current tags
|
||||
// Returns count of tags
|
||||
exports.countTags = function(cb) {
|
||||
db.get('SELECT count(id) as count FROM tags;', function(err, count) {
|
||||
cb(count.count);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get all categories including their ID and descriptions
|
||||
// Returns array of rows of categories
|
||||
exports.listCategories = function(cb) {
|
||||
db.all('SELECT * FROM CATEGORIES', function(err, rows) {
|
||||
cb(rows);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a list of posts of a certain count and starting at an offset
|
||||
// Returns a list of post objects
|
||||
exports.listPosts = function(count, start, cb) {
|
||||
db.all('SELECT * FROM posts ORDER BY postDate DESC, title ASC \
|
||||
LIMIT '+count+' OFFSET '+start+';',
|
||||
function(err, posts) {
|
||||
console.log(err);
|
||||
cb(posts);
|
||||
});
|
||||
}
|
||||
|
||||
// NON-EXPORTED function to create a category if it does not exist
|
||||
// Returns a row of category information
|
||||
var getOrCreateCategory = function(name, cb) {
|
||||
db.get('SELECT * FROM categories WHERE name = "'+name+'";', function(err, row) {
|
||||
if (!err) {
|
||||
cb(row);
|
||||
} else {
|
||||
epoch = helper.dateToEpoch(new Date());
|
||||
db.run('INSERT INTO categories (\
|
||||
name, slug, description, createDate, updatedDate, \
|
||||
lastBuildDate) VALUES (\
|
||||
name = "'+name+'", \
|
||||
slug = "'+helper.makeSlug('name')+'", \
|
||||
createDate = '+epoch+');',
|
||||
function(err, row) {
|
||||
db.get('SELECT * FROM categories WHERE name = "'+name+'";',
|
||||
function(err, row){
|
||||
cb(row);
|
||||
})
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Function to set a category for a post given a post id and a category name
|
||||
// Returns error code
|
||||
exports.setPostCategory = function(postId, categoryName, cb) {
|
||||
getOrCreateCategory(categoryName, function(category) {
|
||||
db.get('SELECT categoryId FROM categoryPosts \
|
||||
WHERE postId = '+postId+';',
|
||||
function(err, row) {
|
||||
if (row) {
|
||||
if (row.categoryId == category.id) {
|
||||
cb(err);
|
||||
} else {
|
||||
db.run('UPDATE categoryPosts SET \
|
||||
categoryId = '+category.id+'\
|
||||
WHERE postId = '+postId+';',
|
||||
function(err, row) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
} else {
|
||||
db.run('INSERT INTO categoryPosts (postId, categoryId) \
|
||||
VALUES ('+postId+', '+category.id+');',
|
||||
function(err, row) {
|
||||
cb(err);
|
||||
});
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
// Function to create a new post
|
||||
// Returns the post id
|
||||
exports.createPost = function(title, slug, markdown, postDate, updatedDate,
|
||||
createDate, cb) {
|
||||
db.run('INSERT INTO posts (\
|
||||
title, \
|
||||
slug, \
|
||||
markdown, \
|
||||
postDate, \
|
||||
createDate, \
|
||||
lastBuildDate, \
|
||||
lastUpload) \
|
||||
VALUES (\
|
||||
"'+title+'", \
|
||||
"'+slug+'", \
|
||||
"'+markdown+'", \
|
||||
"'+postDate+'", \
|
||||
"'+createDate+'", \
|
||||
(strftime("%s","1900-01-01 00:00")*1000), \
|
||||
(strftime("%s","1900-01-01 00:00")*1000));',
|
||||
function(err, row) {
|
||||
db.get('SELECT * FROM posts WHERE title = "'+title+'" AND createDate = '+createDate+';', function(err, row){
|
||||
cb(err, row);
|
||||
})
|
||||
});
|
||||
}
|
||||
|
||||
// Function to update an existing post record
|
||||
// Returns the post object
|
||||
exports.updatePost = function(id, title, slug, markdown, postDate, cb) {
|
||||
console.log("updatePost called.");
|
||||
db.run('UPDATE posts SET \
|
||||
title = "'+title+'", \
|
||||
slug = "'+slug+'", \
|
||||
markdown = "'+markdown+'", \
|
||||
postDate = '+helper.dateToEpoch(postDate)+', \
|
||||
updatedDate = '+helper.dateToEpoch(new Date())+' \
|
||||
WHERE id = '+id+';',
|
||||
function(err) {
|
||||
console.log('updatePost UPDATE result: '+err);
|
||||
db.get('SELECT * FROM posts WHERE id = '+id+';', function(err, row) {
|
||||
console.log('updatePost SELECT result: ' + err);
|
||||
cb(row);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a post record by the id of the post
|
||||
// Returns the post record
|
||||
exports.getPostById = function(id, cb) {
|
||||
db.get('SELECT * FROM posts WHERE id = ?', id, function(err, row) {
|
||||
cb(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get a category record given a post id
|
||||
// Returns the first category record associated with the post.
|
||||
exports.getPostCategory = function(id, cb) {
|
||||
db.get('SELECT * FROM categories WHERE id = (\
|
||||
SELECT categoryId FROM categoryPosts WHERE postId = '+id+');',
|
||||
function(err, row) {
|
||||
cb(row);
|
||||
});
|
||||
}
|
||||
|
||||
// Function to get record of a tag, inserting it if required.
|
||||
// Inputs are a tag name and a callback.
|
||||
// Returns the created tag record.
|
||||
var getOrCreateTag = function(tag, cb) {
|
||||
var slug = helper.makeSlug(tag);
|
||||
db.get('SELECT * from tags WHERE slug = "'+slug+'" LIMIT 1;', function(err, row) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
if (row) {
|
||||
console.log(slug+' tag exists');
|
||||
console.log(row);
|
||||
cb(row);
|
||||
}
|
||||
else {
|
||||
db.run('INSERT INTO tags (name, slug, createDate) \
|
||||
VALUES ("'+tag+'", "'+slug+'", '+ helper.dateToEpoch(new Date()) +');',
|
||||
function(err, id) {
|
||||
if (!err) {
|
||||
console.log(slug+' tag created');
|
||||
db.get('SELECT * FROM tags WHERE slug = "'+slug+'" LIMIT 1;',
|
||||
function (err, row) {
|
||||
if (!err) {
|
||||
console.log(row);
|
||||
cb(row);
|
||||
}
|
||||
else {
|
||||
console.log(err);
|
||||
}
|
||||
});
|
||||
};
|
||||
});
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.getOrCreateTag = getOrCreateTag;
|
||||
|
||||
|
||||
// Function to tag a post with a list of tag names
|
||||
// Inputs: Post ID, List of Tag names
|
||||
// Does not return.
|
||||
exports.tagPost = function (postId, tags) {
|
||||
console.log('Deleting old tags');
|
||||
db.run('DELETE FROM postTags WHERE postId = '+postId+';',
|
||||
function(err) {
|
||||
console.log('Old tags deleted');
|
||||
for (var i = 0, size = tags.length; i < size; i++) {
|
||||
getOrCreateTag(tags[i], function(row) {
|
||||
db.run('INSERT INTO postTags (postId, tagId) \
|
||||
VALUES ('+postId+', '+row.id+');',
|
||||
function(err) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
console.log('Post '+postId+' tagged as '+row.name);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
// Function to get tag ids associated with a particular post id
|
||||
// Inputs: Post ID
|
||||
// Returns: Records associated with tags of that post id
|
||||
var getPostTags = function (postId, cb) {
|
||||
db.all('SELECT tagId FROM postTags WHERE postId = '+postId+';',
|
||||
function(err, rows) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
var tagList = [];
|
||||
for (row in rows) {
|
||||
tagList.push(rows[row].tagId);
|
||||
}
|
||||
tagList = tagList.join(', ');
|
||||
console.log('Tag ids for '+postId+': '+tagList);
|
||||
db.all('SELECT * FROM tags WHERE id IN ('+tagList+');',
|
||||
function(err, rows) {
|
||||
if (err) {
|
||||
console.log(err);
|
||||
}
|
||||
else {
|
||||
cb(rows);
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
exports.getPostTags = getPostTags;
|
||||
|
||||
|
||||
// Function that returns all the post tags as a space separated string.
|
||||
// Inputs: Post ID, callback function.
|
||||
// Returns: callback of string with all tags separated by spaces.
|
||||
var getPostTagsAsString = function(postId, cb) {
|
||||
getPostTags(postId, function(tags) {
|
||||
var str = false;
|
||||
for (tag in tags) {
|
||||
if (!str) {
|
||||
str = tags[tag].name;
|
||||
}
|
||||
else {
|
||||
str = str + ' ' + tags[tag].name;
|
||||
}
|
||||
}
|
||||
console.log("Tags for "+postId+": "+str);
|
||||
cb(str);
|
||||
})
|
||||
}
|
||||
|
||||
exports.getPostTagsAsString = getPostTagsAsString;
|
64
helper.js
Normal file
64
helper.js
Normal file
|
@ -0,0 +1,64 @@
|
|||
// Helper functions
|
||||
var crypto = require('crypto');
|
||||
|
||||
// sha256 hex-hashes password with supplied salt.
|
||||
// Returns hex-hash string
|
||||
exports.hashPassword = function(password, salt) {
|
||||
var hash = crypto.createHash('sha256');
|
||||
hash.update(password);
|
||||
hash.update(salt);
|
||||
return hash.digest('hex');
|
||||
}
|
||||
|
||||
// Convert date() to epoch
|
||||
exports.dateToEpoch = function(date) {
|
||||
return Math.floor(date);
|
||||
};
|
||||
|
||||
// Convert epoch (in seconds) to date string
|
||||
exports.epochToDateString = function(epoch) {
|
||||
var date = new Date(epoch);
|
||||
return date.toLocaleString();
|
||||
};
|
||||
|
||||
// Convert epoch to an ISO formatted date.
|
||||
exports.epochToShortDateString = function(epoch) {
|
||||
var date = new Date(epoch);
|
||||
return date.toISOString().slice(0,10);
|
||||
}
|
||||
|
||||
// Build a slug from a title string
|
||||
// Returns the slug as a string
|
||||
exports.makeSlug = function(str) {
|
||||
str = str.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;
|
||||
};
|
||||
|
||||
// Parse a string of tags
|
||||
// Returns a list of tags
|
||||
exports.parseTags = function(str) {
|
||||
console.log('tag parse '+str);
|
||||
str = str.replace(/,/g,'');
|
||||
console.log('tag parse '+str);
|
||||
var list = str.split(" ");
|
||||
console.log('tag parse '+list);
|
||||
for (var i = 0, size = list.length; i < size; i++) {
|
||||
list[i] = list[i].trim();
|
||||
}
|
||||
console.log('tag parse '+list);
|
||||
return list
|
||||
}
|
22
package.json
Normal file
22
package.json
Normal file
|
@ -0,0 +1,22 @@
|
|||
{
|
||||
"name": "amdavidson.com",
|
||||
"version": "0.0.0",
|
||||
"private": true,
|
||||
"dependencies": {
|
||||
"body-parser": "~1.13.2",
|
||||
"cookie-parser": "~1.3.5",
|
||||
"debug": "~2.2.0",
|
||||
"express": "~4.13.1",
|
||||
"express-session": "~1.12.0",
|
||||
"express-flash": "~0.0.2",
|
||||
"jade": "~1.11.0",
|
||||
"morgan": "~1.6.1",
|
||||
"serve-favicon": "~2.3.0",
|
||||
"passport": "~0.3.0",
|
||||
"passport-local": "~1.0.0",
|
||||
"connect-ensure-login": "~0.1.1",
|
||||
"sqlite3": "~3.1.0",
|
||||
"async": "~1.5.0",
|
||||
"markdown": "~0.5.0"
|
||||
}
|
||||
}
|
66
public/stylesheets/style.css
Normal file
66
public/stylesheets/style.css
Normal file
|
@ -0,0 +1,66 @@
|
|||
/* Site Specific CSS */
|
||||
|
||||
/* Bump the body down to account for nav bar */
|
||||
body {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
/* Some body padding, need more whitespace */
|
||||
|
||||
.main {
|
||||
padding: 20px;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.main {
|
||||
padding-right: 40px;
|
||||
padding-left: 40px;
|
||||
}
|
||||
}
|
||||
.main .page-header {
|
||||
margin-top: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
/* Hide for mobile, show later */
|
||||
.sidebar {
|
||||
display: none;
|
||||
}
|
||||
@media (min-width: 768px) {
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 51px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
display: block;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto;
|
||||
background-color: #f5f5f5;
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
}
|
||||
|
||||
/* Sidebar navigation */
|
||||
.nav-sidebar {
|
||||
margin-right: -21px; /* 20px padding + 1px border */
|
||||
margin-bottom: 20px;
|
||||
margin-left: -20px;
|
||||
}
|
||||
.nav-sidebar > li > a {
|
||||
padding-right: 20px;
|
||||
padding-left: 20px;
|
||||
}
|
||||
.nav-sidebar > form > button {
|
||||
margin-left: 20px;
|
||||
}
|
||||
|
||||
.nav-sidebar > .active > a,
|
||||
.nav-sidebar > .active > a:hover,
|
||||
.nav-sidebar > .active > a:focus {
|
||||
color: #fff;
|
||||
background-color: #428bca;
|
||||
}
|
123
schema.sql
Normal file
123
schema.sql
Normal file
|
@ -0,0 +1,123 @@
|
|||
-- SQL Schema for Crunch
|
||||
|
||||
-- Note all dates must be in epoch seconds to allow for sqlite comparison
|
||||
|
||||
-- User table
|
||||
|
||||
DROP TABLE IF EXISTS "users";
|
||||
|
||||
CREATE TABLE "users" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"username" TEXT NOT NULL UNIQUE,
|
||||
"password" TEXT NOT NULL, -- sha256 hash of plaintext password + salt
|
||||
"salt" TEXT NOT NULL, -- salt that is appended to the password
|
||||
"email" TEXT UNIQUE,
|
||||
"displayName" TEXT,
|
||||
"createDate" TEXT
|
||||
);
|
||||
|
||||
-- Content Tables
|
||||
|
||||
DROP TABLE IF EXISTS "posts";
|
||||
|
||||
CREATE TABLE "posts" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"title" TEXT, -- The title of the post
|
||||
"slug" TEXT NOT NULL, -- A slug form of the title or id
|
||||
"markdown" TEXT NOT NULL, -- The post content in markdown format
|
||||
"postDate" INTEGER, -- The date that the post should go live and be sorted by
|
||||
"updatedDate" INTEGER, -- The date that the post was last updated
|
||||
"createDate" INTEGER NOT NULL, -- The date the post was created
|
||||
"lastBuildDate" INTEGER, -- The date this post was last built
|
||||
"lastUpload" INTEGER -- The date the post was last uploaded
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "galleries";
|
||||
|
||||
CREATE TABLE "galleries" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"title" TEXT, -- The title of the gallery
|
||||
"slug" TEXT NOT NULL UNIQUE, -- A slug form of the title or id
|
||||
"description" TEXT NOT NULL, -- The gallery description in markdown format
|
||||
"postDate" INTEGER, -- The date that the gallery should go live and be sorted by
|
||||
"updatedDate" INTEGER, -- The date that the gallery was last updated
|
||||
"createDate" INTEGER NOT NULL, -- The date the gallery was created
|
||||
"lastBuildDate" INTEGER, -- The date this gallery was last built
|
||||
"lastUpload" INTEGER -- The date the gallery was last uploaded
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "photos";
|
||||
|
||||
CREATE TABLE "photos" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"path" TEXT NOT NULL, -- The path of the photo location on disk
|
||||
"title" TEXT, -- The title of the photo
|
||||
"slug" TEXT NOT NULL UNIQUE, -- A slug form of the title or id
|
||||
"description" TEXT NOT NULL, -- The photo description in markdown format
|
||||
"photoDate" INTEGER, -- The date that the photo should go live and be sorted by
|
||||
"updatedDate" INTEGER, -- The date that the photo was last updated
|
||||
"createDate" INTEGER NOT NULL, -- The date the photo was created
|
||||
"lastBuildDate" INTEGER, -- The date this photo was last built
|
||||
"lastUpload" INTEGER -- The date the photo was last uploaded
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "galleryPhotos";
|
||||
|
||||
CREATE TABLE "galleryPhotos" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"galleryId" INTEGER NOT NULL, -- the id of the gallery
|
||||
"photoId" INTEGER NOT NULL, -- the id of the photo
|
||||
FOREIGN KEY(galleryId) REFERENCES galleries(id),
|
||||
FOREIGN KEY(photoId) REFERENCES photos(id)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "categories";
|
||||
|
||||
CREATE TABLE "categories" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"name" TEXT NOT NULL, -- The name of this category
|
||||
"slug" TEXT NOT NULL UNIQUE, -- A slug form of the name or id
|
||||
"description" TEXT, -- A brief description of the category in markdown
|
||||
"createDate" INTEGER NOT NULL -- The date the category was created
|
||||
);
|
||||
|
||||
INSERT INTO "categories" VALUES (1, "Uncategorized", "uncategorized", "Uncategorized posts", (strftime('%s','now')*1000));
|
||||
|
||||
DROP TABLE IF EXISTS "categoryPosts";
|
||||
|
||||
CREATE TABLE "categoryPosts" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"categoryId" INTEGER NOT NULL, -- the id of the category
|
||||
"postId" INTEGER NOT NULL, -- the id of the post
|
||||
FOREIGN KEY(categoryId) REFERENCES categories(id),
|
||||
FOREIGN KEY(postId) REFERENCES posts(id)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "tags";
|
||||
|
||||
CREATE TABLE "tags" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"name" TEXT NOT NULL, -- The name of this tag
|
||||
"slug" TEXT NOT NULL UNIQUE, -- A slug form of the name or id
|
||||
"createDate" INTEGER NOT NULL -- The date the tag was created
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "postTags";
|
||||
|
||||
CREATE TABLE "postTags" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"postId" INTEGER NOT NULL, -- the id of the post
|
||||
"tagId" INTEGER NOT NULL, -- the id of the tag
|
||||
FOREIGN KEY(postId) REFERENCES posts(id),
|
||||
FOREIGN KEY(tagId) REFERENCES tags(id)
|
||||
);
|
||||
|
||||
DROP TABLE IF EXISTS "photoTags";
|
||||
|
||||
CREATE TABLE "photoTags" (
|
||||
"id" INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
"photoId" INTEGER NOT NULL, -- the id of the photo
|
||||
"tagId" INTEGER NOT NULL, -- the id of the tag
|
||||
FOREIGN KEY(photoId) REFERENCES photos(id),
|
||||
FOREIGN KEY(tagId) REFERENCES tags(id)
|
||||
);
|
75
views/admin-dashboard.jade
Normal file
75
views/admin-dashboard.jade
Normal file
|
@ -0,0 +1,75 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
div(class="row")
|
||||
include ./admin-sidebar.jade
|
||||
|
||||
div(class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main")
|
||||
h1(class="page-header") Crunch Dashboard
|
||||
|
||||
include ./admin-messages.jade
|
||||
|
||||
div(class="row")
|
||||
div(class="col-md-6")
|
||||
div(class="panel panel-success")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Last Upload (UTC)
|
||||
|
||||
div(class="panel-body")
|
||||
h3 #{uploadDate}
|
||||
|
||||
div(class="col-md-6")
|
||||
div(class="panel panel-danger")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Last Rebuild (UTC)
|
||||
|
||||
div(class="panel-body")
|
||||
h3 #{rebuildDate}
|
||||
|
||||
div(class="row")
|
||||
div(class="col-md-4")
|
||||
div(class="panel panel-info")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Number of Posts
|
||||
div(class="panel-body")
|
||||
h3: a(href="/admin/post/list") #{posts}
|
||||
form(action="/admin/post/new", method="get")
|
||||
button(class="btn btn-sm btn-primary") New Post
|
||||
|
||||
|
||||
div(class="col-md-4")
|
||||
div(class="panel panel-info")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Number of Galleries
|
||||
|
||||
div(class="panel-body")
|
||||
h3: a(href="/admin/gallery/list") #{galleries}
|
||||
form(action="/admin/gallery/new", method="get")
|
||||
button(class="btn btn-sm btn-primary") New Gallery
|
||||
|
||||
div(class="col-md-4")
|
||||
div(class="panel panel-info")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Number of Photos
|
||||
|
||||
div(class="panel-body")
|
||||
h3: a(href="/admin/photo/list") #{photos}
|
||||
form(action="/admin/photo/new", method="get")
|
||||
button(class="btn btn-sm btn-primary") New Photo
|
||||
|
||||
div(class="row")
|
||||
div(class="col-md-6")
|
||||
div(class="panel panel-info")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Number of Tags
|
||||
|
||||
div(class="panel-body")
|
||||
h3 #{tags}
|
||||
|
||||
div(class="col-md-6")
|
||||
div(class="panel panel-info")
|
||||
div(class="panel-heading")
|
||||
h3(class="panel-title") Number of Categories
|
||||
|
||||
div(class="panel-body")
|
||||
h3: a(href="/admin/category/list") #{categories}
|
6
views/admin-error.jade
Normal file
6
views/admin-error.jade
Normal file
|
@ -0,0 +1,6 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
h1= message
|
||||
h2= error.status
|
||||
pre #{error.stack}
|
10
views/admin-index.jade
Normal file
10
views/admin-index.jade
Normal file
|
@ -0,0 +1,10 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
div(class="page-header row col-sm-10")
|
||||
h1 Crunch
|
||||
div(class="row col-sm-10")
|
||||
include ./admin-messages.jade
|
||||
|
||||
div
|
||||
p If you don't know why you're here, please head #[a(href='http://youtube.com') somewhere else]
|
20
views/admin-layout.jade
Normal file
20
views/admin-layout.jade
Normal file
|
@ -0,0 +1,20 @@
|
|||
doctype html
|
||||
html
|
||||
head
|
||||
title= title
|
||||
meta(charset="utf-8")
|
||||
meta(http-equiv="X-UA-Compatible", content="IE=edge")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1")
|
||||
//-link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css", rel="stylesheet" integrity="sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc= sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous")
|
||||
link(href="https://maxcdn.bootstrapcdn.com/bootswatch/3.3.5/yeti/bootstrap.min.css", rel="stylesheet" integrity="sha256-gJ9rCvTS5xodBImuaUYf1WfbdDKq54HCPz9wk8spvGs= sha512-weqt+X3kGDDAW9V32W7bWc6aSNCMGNQsdOpfJJz/qD/Yhp+kNeR+YyvvWojJ+afETB31L0C4eO0pcygxfTgjgw==" crossorigin="anonymous")
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
block head-addition
|
||||
body(role="document")
|
||||
include ./admin-navbar.jade
|
||||
div(class="container-fluid", role="main")
|
||||
block content
|
||||
|
||||
script(src="https://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.min.js")
|
||||
script(src="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/js/bootstrap.min.js" integrity="sha256-Sk3nkD6mLTMOF0EOpNtsIry+s1CsaqQC1rVLTAy+0yc= sha512-K1qjQ+NcF2TYO/eI3M6v8EiNYZfA95pQumfvcVrTHtwQVDG+aHRqLi/ETn2uB+1JqwYqVG3LIvdm9lj6imS/pQ==" crossorigin="anonymous")
|
||||
|
||||
block footer
|
14
views/admin-login.jade
Normal file
14
views/admin-login.jade
Normal file
|
@ -0,0 +1,14 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
div(class="page-header row col-sm-6 col-sm-offset-3")
|
||||
h1 Login
|
||||
div(class="row col-sm-6 col-sm-offset-3")
|
||||
form(action="/login", method="post")
|
||||
div(class="form-group form-horizontal")
|
||||
label(class="control-label", for="username") Username:
|
||||
input(class="form-control", type="text", name="username")
|
||||
div(class="form-group")
|
||||
label(class="control-label", for="password") Password:
|
||||
input(class="form-control", type="password", name="password")
|
||||
button(class="btn btn-success", type="submit") Log In
|
23
views/admin-messages.jade
Normal file
23
views/admin-messages.jade
Normal file
|
@ -0,0 +1,23 @@
|
|||
if (failureNotice)
|
||||
if (failureNotice.length > 0)
|
||||
div(class="alert alert-danger alert-dismissible" role="alert")
|
||||
button(type="button" class="close" data-dismiss="alert" aria-label="Close")
|
||||
span(aria-hidden="true") ×
|
||||
strong Failure!
|
||||
| #{failureNotice}
|
||||
|
||||
if (warningNotice)
|
||||
if (warningNotice.length > 0)
|
||||
div(class="alert alert-warning alert-dismissible" role="alert")
|
||||
button(type="button" class="close" data-dismiss="alert" aria-label="Close")
|
||||
span(aria-hidden="true") ×
|
||||
strong Warning!
|
||||
| #{warningNotice}
|
||||
|
||||
if (successNotice)
|
||||
if (successNotice.length > 0)
|
||||
div(class="alert alert-success alert-dismissible" role="alert")
|
||||
button(type="button" class="close" data-dismiss="alert" aria-label="Close")
|
||||
span(aria-hidden="true") ×
|
||||
strong Success!
|
||||
| #{successNotice}
|
34
views/admin-navbar.jade
Normal file
34
views/admin-navbar.jade
Normal file
|
@ -0,0 +1,34 @@
|
|||
nav(class="navbar navbar-inverse navbar-fixed-top")
|
||||
div(class="container-fluid")
|
||||
div(class="navbar-header")
|
||||
button(type="button", class="navbar-toggle collapsed", data-toggle="collapse", data-target="#navbar", aria-expanded="false", aria-controls="navbar")
|
||||
span(class="sr-only") Toggle Navbar
|
||||
span(class="icon-bar")
|
||||
span(class="icon-bar")
|
||||
span(class="icon-bar")
|
||||
a(class="navbar-brand", href="#") Crunch
|
||||
div(id="navbar", class="collapse navbar-collapse")
|
||||
ul(class="nav navbar-nav")
|
||||
if(!user)
|
||||
li
|
||||
a(href="/")
|
||||
span(class="glyphicon glyphicon-home") Home
|
||||
li
|
||||
a(href="/login")
|
||||
span(class="glyphicon glyphicon-log-in") Login
|
||||
if(user)
|
||||
li
|
||||
a(href="/admin")
|
||||
span(class="glyphicon glyphicon-home") Dashboard
|
||||
li
|
||||
a(href="/admin/post/new")
|
||||
span(class="glyphicon glyphicon-file") Post
|
||||
li
|
||||
a(href="/admin/photo/new")
|
||||
span(class="glyphicon glyphicon-picture") Photo
|
||||
li
|
||||
a(href="/admin/gallery/new")
|
||||
span(class="glyphicon glyphicon-th-large") Gallery
|
||||
li
|
||||
a(href="/logout")
|
||||
span(class="glyphicon glyphicon-log-out") Logout
|
39
views/admin-post-edit.jade
Normal file
39
views/admin-post-edit.jade
Normal file
|
@ -0,0 +1,39 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
div(class="row")
|
||||
include ./admin-sidebar.jade
|
||||
|
||||
div(class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main")
|
||||
h1(class="page-header") Edit Post
|
||||
|
||||
include ./admin-messages.jade
|
||||
|
||||
form(method="post", action="/admin/post/edit/#{post.id}")
|
||||
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.title}")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
input(class="form-control", type="text", name="slug", placeholder="Enter slug (optional)", value="#{post.slug}")
|
||||
div(class="row page-header")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
div(class="input-group-btn")
|
||||
button(type="button", class="btn btn-primary dropdown-toggle", data-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Category
|
||||
ul(class="dropdown-menu")
|
||||
each row in categories
|
||||
li: a(href="#", onClick="document.getElementById('category').value = '#{row.name}'") #{row.name}
|
||||
input(type="text", class="form-control", aria-label="Post category", name="category", id="category" value="#{postCategory.name}")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
input(type="text", class="form-control", placeholder="Publish Date", name="postDate", value="#{formattedDate}")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
input(type="text", class="form-control", placeholder="Tags", name="tags" value="#{postTags}")
|
||||
div(class="row page-header")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
textarea(class="form-control", rows="12", name="markdown")
|
||||
| #{post.markdown}
|
||||
div(class="row")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
div(class="pull-left")
|
||||
button(type="submit", class="btn btn-lg btn-success", name="submit") Update Post
|
||||
div(class="pull-right")
|
||||
button(type="submit", class="btn btn-lg btn-danger", name="submit") Delete Post
|
27
views/admin-post-list.jade
Normal file
27
views/admin-post-list.jade
Normal file
|
@ -0,0 +1,27 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
div(class="row")
|
||||
include ./admin-sidebar.jade
|
||||
|
||||
div(class="col-sm-9 col-sm-offset-3 col-md-10 col-md-offset-2 main")
|
||||
h1(class="page-header") Posts
|
||||
|
||||
include ./admin-messages.jade
|
||||
|
||||
table(class="table table-striped")
|
||||
each post in posts
|
||||
tr
|
||||
td #{post.id}
|
||||
td #{post.title}
|
||||
td #{post.dateString}
|
||||
td
|
||||
a(href="/admin/post/edit/#{post.id}") Edit Post
|
||||
| -
|
||||
a(href="/admin/post/delete/#{post.id}") Delete Post
|
||||
| -
|
||||
a(href="/admin/post/rebuild/#{post.id}") Force Rebuild
|
||||
| -
|
||||
a(href="/admin/post/publish/#{post.id}") Force Publish
|
||||
|
||||
|
36
views/admin-post-new.jade
Normal file
36
views/admin-post-new.jade
Normal file
|
@ -0,0 +1,36 @@
|
|||
extends admin-layout
|
||||
|
||||
block content
|
||||
div(class="row")
|
||||
include ./admin-sidebar.jade
|
||||
|
||||
div(class="col-sm-10 col-sm-offset-2 col-md-10 col-md-offset-2 main")
|
||||
h1(class="page-header") New Post
|
||||
|
||||
include ./admin-messages.jade
|
||||
|
||||
form(method="post", action="/admin/post/new")
|
||||
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")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
input(class="form-control", type="text", name="slug", placeholder="Enter slug (optional)")
|
||||
div(class="row page-header")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
div(class="input-group-btn")
|
||||
button(type="button", class="btn btn-primary dropdown-toggle", data-toggle="dropdown", aria-haspopup="true", aria-expanded="false") Category
|
||||
ul(class="dropdown-menu")
|
||||
each row in categories
|
||||
li: a(href="#", onClick="document.getElementById('category').value = '#{row.name}'") #{row.name}
|
||||
input(type="text", class="form-control", aria-label="Post category", name="category", id="category", value="Uncategorized")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
input(type="text", class="form-control", placeholder="Publish Date", name="postDate")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
input(type="text", class="form-control", placeholder="Tags", name="tags")
|
||||
div(class="row page-header")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1")
|
||||
textarea(class="form-control", rows="12", name="markdown", placeholder="Markdown formatted content")
|
||||
div(class="row")
|
||||
div(class="input-group col-xs-10 col-xs-offset-1 col-md-10 col-md-offset-1 centern")
|
||||
div(class="pull-left")
|
||||
button(type="submit", class="btn btn-lg btn-success", name="submit") Create Post
|
25
views/admin-sidebar.jade
Normal file
25
views/admin-sidebar.jade
Normal file
|
@ -0,0 +1,25 @@
|
|||
div(class="col-sm-3 col-md-2 sidebar")
|
||||
ul(class="nav nav-sidebar")
|
||||
li: a(href="/admin/") Dashboard
|
||||
|
||||
ul(class="nav nav-sidebar")
|
||||
li: a(href="/admin/post/new") New Post
|
||||
li: a(href="/admin/post/list") Edit Post
|
||||
|
||||
ul(class="nav nav-sidebar")
|
||||
li: a(href="/admin/category/new") New Category
|
||||
li: a(href="/admin/category/list") Edit Category
|
||||
|
||||
ul(class="nav nav-sidebar")
|
||||
li: a(href="/admin/gallery/new") New Gallery
|
||||
li: a(href="/admin/gallery/list") Edit Gallery
|
||||
|
||||
ul(class="nav nav-sidebar")
|
||||
form(action="/admin/rebuild/", method="get")
|
||||
button(class="btn btn-lg btn-success")
|
||||
span(class="glyphicon glyphicon-repeat") Build
|
||||
|
||||
ul(class="nav nav-sidebar")
|
||||
form(action="/admin/publish/", method="get")
|
||||
button(class="btn btn-lg btn-warning")
|
||||
span(class="glyphicon glyphicon-upload") Upload
|
7
views/render-index.jade
Normal file
7
views/render-index.jade
Normal file
|
@ -0,0 +1,7 @@
|
|||
extends render-layout
|
||||
|
||||
block content
|
||||
each post in posts
|
||||
h3: a(href="#{url}") #{title}
|
||||
p #{postDate}
|
||||
div !{content}
|
13
views/render-layout.jade
Normal file
13
views/render-layout.jade
Normal file
|
@ -0,0 +1,13 @@
|
|||
doctype html
|
||||
html
|
||||
head
|
||||
title #{title}
|
||||
meta(charset="utf-8")
|
||||
meta(http-equiv="X-UA-Compatible", content="IE=edge")
|
||||
meta(name="viewport", content="width=device-width, initial-scale=1")
|
||||
link(href="https://maxcdn.bootstrapcdn.com/bootstrap/3.3.5/css/bootstrap.min.css", rel="stylesheet" integrity="sha256-MfvZlkHCEqatNoGiOXveE8FIwMzZg4W85qfrfIFBfYc= sha512-dTfge/zgoMYpP7QbHy4gWMEGsbsdZeCXz7irItjcC3sPUFtf0kuFbDz/ixG7ArTxmDjLXDmezHubeNikyKGVyQ==" crossorigin="anonymous")
|
||||
link(rel='stylesheet', href='/stylesheets/style.css')
|
||||
block head-addition
|
||||
body(role="document")
|
||||
div(class="container-fluid", role="main")
|
||||
block content
|
6
views/render-post.jade
Normal file
6
views/render-post.jade
Normal file
|
@ -0,0 +1,6 @@
|
|||
extends render-layout
|
||||
|
||||
block content
|
||||
h3: a(href="#{url}") #{title}
|
||||
p #{postDate}
|
||||
div !{content}
|
Loading…
Reference in a new issue