diff --git a/.gitignore b/.gitignore index 8454d66..642e82c 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,5 @@ node_modules preload.sql dev.db + +tmp/ diff --git a/app.js b/app.js index 048ebeb..ea2a43c 100644 --- a/app.js +++ b/app.js @@ -8,8 +8,14 @@ var multer = require('multer'); var fs = require('fs'); var markdown = require( "markdown" ).markdown; + +// Get connected to our database +var MongoClient = require('mongodb').MongoClient + +Post = require('./post.js'); +User = require('./user.js'); var helper = require('./helper.js'); -var db = require('./db.js'); +var database = require('./db.js'); var genPosts = require('./genPosts.js'); var genStatic = require('./genStatic.js'); var genPhotos = require('./genPhotos.js'); @@ -33,8 +39,11 @@ 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(require('express-session')({ + secret: 'amdasdfasdfamd', + resave: true, + saveUninitialized: true }) +); app.use(express.static(path.join(__dirname, 'public'))); var upload = multer({ dest: config.uploadDir }); @@ -42,7 +51,8 @@ var upload = multer({ dest: config.uploadDir }); // Setup authentication via twitter. passport.use(new Strategy( function(username, password, done) { - db.getUser(username, password, function(user) { + User.verify(username, password, function(err, user) { + console.log("Verifying user: " + username); if (!user) { return done(null, false); } return done(null, user); }); @@ -50,11 +60,13 @@ passport.use(new Strategy( )); passport.serializeUser(function(user, done) { - done(null, user.id); + console.log("Serializing user: "+user.username); + done(null, user.username); }); -passport.deserializeUser(function(id, done) { - db.getUserById(id, function (user) { +passport.deserializeUser(function(username, done) { + console.log("Deserializing user: "+username); + User.getByUsername(username, function (err, user) { if (!user) { return done(false); } done(null, user); }); @@ -63,8 +75,8 @@ passport.deserializeUser(function(id, done) { app.use(passport.initialize()); app.use(passport.session()); -// Require logins for all admin pages. -app.all('/admin/*', require('connect-ensure-login').ensureLoggedIn()); +// Require logins for all admin pages other pages have to be handled separately. +app.all('/admin*', require('connect-ensure-login').ensureLoggedIn()); // User management routing app.get('/login', function(req, res) { @@ -78,7 +90,8 @@ app.post('/login', app.get('/logout', function(req, res) { req.logout(); res.redirect('/'); -}); +} +); // Post management routing app.get('/admin/post/list/:start?', @@ -89,7 +102,7 @@ app.get('/admin/post/list/:start?', } else { var start = 0; } - db.listPosts(count, start, function(posts){ + database.listPosts(count, start, function(posts){ for (post in posts) { var date = new Date(posts[post].postDate); posts[post].dateString = date.getFullYear() + '-' + @@ -98,12 +111,13 @@ app.get('/admin/post/list/:start?', } res.render('admin-post-list', {posts, user: req.user}); }); -}); +} +); app.get('/admin/post/view/:id?', function (req, res, next) { if (req.params.id) { - db.getPostById(req.params.id, function(row) { + database.getPostById(req.params.id, function(row) { res.render('admin-post-view', { successNotice: req.flash('successNotice'), failureNotice: req.flash('failureNotice'), @@ -122,10 +136,11 @@ app.get('/admin/post/view/:id?', app.get('/admin/post/new', function(req, res, next) { - db.listCategories(function(rows){ + database.listCategories(function(rows){ res.render('admin-post-new', { categories: rows, user: req.user }); }); -}); +} +); app.post('/admin/post/new', function(req, res, next) { @@ -146,36 +161,37 @@ app.post('/admin/post/new', var categoryName = req.body.category; var tags = helper.parseTags(req.body.tags); - db.createPost(title, slug, markdown, postDate, updatedDate, createDate, function(err, row) { - db.setPostCategory(row.id, categoryName, function(category) { - db.tagPost(row.id, tags); + database.createPost(title, slug, markdown, postDate, updatedDate, createDate, function(err, row) { + database.setPostCategory(row.id, categoryName, function(category) { + database.tagPost(row.id, tags); req.flash('successNotice', 'Post created.'); res.redirect('/admin/post/edit/'+row.id); }); }); -}); +} +); app.get('/admin/post/edit/:id', function(req, res, next) { var id = req.params.id; async.parallel({ categories: function(callback) { - db.listCategories(function(categories) { + database.listCategories(function(categories) { callback(null, categories); }) }, postCategory: function(callback) { - db.getPostCategory(id, function(category) { + database.getPostCategory(id, function(category) { callback(null, category); }) }, postTags: function(callback) { - db.getPostTagsAsString(id, function(tagString) { + database.getPostTagsAsString(id, function(tagString) { callback(null, tagString); }) }, post: function(callback) { - db.getPostById(id, function(post) { + database.getPostById(id, function(post) { callback(null, post); }) } @@ -197,7 +213,8 @@ app.get('/admin/post/edit/:id', user: req.user }); }); -}); +} +); app.post('/admin/post/edit/:id', function(req, res, next) { @@ -209,14 +226,15 @@ app.post('/admin/post/edit/:id', var categoryName = req.body.category; var tags = helper.parseTags(req.body.tags); console.log('Post '+id+' update request received'); - db.tagPost(id, tags); - db.updatePost(id, title, slug, markdown, postDate, function(err, row) { - db.setPostCategory(id, categoryName, function(err) { + database.tagPost(id, tags); + database.updatePost(id, title, slug, markdown, postDate, function(err, row) { + database.setPostCategory(id, categoryName, function(err) { req.flash('successNotice', 'Post updated.'); res.redirect('/admin/post/edit/'+id); }); }); -}); +} +); app.get('/admin/post/regenerate/:id?', function(req, res, next) { @@ -237,7 +255,8 @@ app.get('/admin/post/regenerate/:id?', res.redirect('/admin/post/list'); } -}); +} +); // Photo management Routing app.get('/admin/photo/list/:start?', @@ -248,7 +267,7 @@ app.get('/admin/photo/list/:start?', } else { var start = 0; } - db.listPhotos(count, start, function(photos){ + database.listPhotos(count, start, function(photos){ for (photo in photos) { var date = new Date(photos[photo].photoDate); photos[photo].dateString = date.getFullYear() + '-' + @@ -257,12 +276,13 @@ app.get('/admin/photo/list/:start?', } res.render('admin-photo-list', {photos, user: req.user}); }); -}); +} +); app.get('/admin/photo/view/:id?', function (req, res, next) { if (req.params.id) { - db.getPhotoById(req.params.id, function(row) { + database.getPhotoById(req.params.id, function(row) { res.render('admin-photo-view', { successNotice: req.flash('successNotice'), failureNotice: req.flash('failureNotice'), @@ -275,13 +295,14 @@ app.get('/admin/photo/view/:id?', else { res.redirect('/admin/photo/list'); } - } +} ); app.get('/admin/photo/new', function(req, res, next) { res.render('admin-photo-new', { user: req.user }); -}) +} +); app.post('/admin/photo/new', upload.single('photo'), @@ -318,26 +339,27 @@ app.post('/admin/photo/new', var categoryName = req.body.category; var path = req.file.path; - db.createPhoto(title, slug, markdown, extension, mimetype, photoDate, updatedDate, createDate, path, function(err, row) { + database.createPhoto(title, slug, markdown, extension, mimetype, photoDate, updatedDate, createDate, path, function(err, row) { if (err) console.log(err); console.log(row); - db.tagPhoto(row.id, tags); + database.tagPhoto(row.id, tags); req.flash('successNotice', 'Photo created.'); res.redirect('/admin/photo/view/'+row.id); }); -}); +} +); app.get('/admin/photo/edit/:id', function(req, res, next) { var id = req.params.id; async.parallel({ photoTags: function(callback) { - db.getPhotoTagsAsString(id, function(tagString) { + database.getPhotoTagsAsString(id, function(tagString) { callback(null, tagString); }) }, photo: function(callback) { - db.getPhotoById(id, function(photo) { + database.getPhotoById(id, function(photo) { callback(null, photo); }) } @@ -353,7 +375,8 @@ app.get('/admin/photo/edit/:id', user: req.user }); }); -}); +} +); app.post('/admin/photo/edit/:id', function(req, res, next) { @@ -364,65 +387,76 @@ app.post('/admin/photo/edit/:id', var photoDate = helper.dateToEpoch(new Date(req.body.photoDate)); var tags = helper.parseTags(req.body.tags); console.log('Post '+id+' update request received'); - db.tagPhoto(id, tags); - db.updatePhoto(id, title, slug, markdown, photoDate, function(err, row) { + database.tagPhoto(id, tags); + database.updatePhoto(id, title, slug, markdown, photoDate, function(err, row) { req.flash('successNotice', 'Photo updated.'); res.redirect('/admin/photo/view/'+id); }); -}); +} +); app.get('/admin/photo/regenerate/:id', function(req, res, next) { console.log('Generating resized images for: '+req.params.id); + genStatic.generateStatic(function(err){ if (err) console.log(err) }); genPhotos.generatePhotoSizesById(req.params.id, function(err) { if (!err) { - req.flash('successNotice', 'Resized images created.'); - res.redirect('/admin/photo/view/'+req.params.id); + genPhotos.generatePhotoPage(req.params.id, function(err) { + if (!err) { + req.flash("successNotice", "Photo regenerated"); + res.redirect('/admin/photo/view/'+req.params.id); + } + else { + req.flash('failureNotice', 'Photo was unable to be resized.'); + res.redirect('/admin/photo/view/'+req.params.id); + } + }) } else { req.flash('failureNotice', 'Photo was unable to be resized.'); res.redirect('/admin/photo/view/'+req.params.id); } }); -}); +} +); // Admin dashboard page. app.get('/admin', function(req, res, next) { async.parallel({ categories: function(callback) { - db.countCategories(function(count) { + database.countCategories(function(count) { callback(null, count); }); }, posts: function(callback) { - db.countPosts(function(count) { + database.countPosts(function(count) { callback(null, count); }); }, galleries: function(callback) { - db.countGalleries(function(count) { + database.countGalleries(function(count) { callback(null, count); }); }, photos: function(callback) { - db.countPhotos(function(count) { + database.countPhotos(function(count) { callback(null, count); }); }, tags: function(callback) { - db.countTags(function(count) { + database.countTags(function(count) { callback(null, count); }); }, regenerateDate: function(callback) { - db.getLastRegenerateDate(function(date) { + database.getLastRegenerateDate(function(date) { var dateString = helper.epochToDateString(date.date).slice(0,-12); callback(null, dateString); }); }, uploadDate: function(callback) { - db.getLastUploadDate(function(date) { + database.getLastUploadDate(function(date) { var dateString = helper.epochToDateString(date.date).slice(0,-12); callback(null, dateString); }); @@ -442,7 +476,8 @@ app.get('/admin', user: req.user}); } ); -}); +} +); // Routes for previewing sent versions of the built items. app.get('/blog/*', @@ -470,7 +505,7 @@ app.get('/blog/*', } }); } - } +} ); app.get('/photos/*', @@ -526,7 +561,7 @@ app.get('/galleries/*', } }); } - } +} ); app.get('/static/*', @@ -554,7 +589,7 @@ app.get('/static/*', } }); } - } +} ); app.get('/'+config.uploadDir+'/*', @@ -585,11 +620,11 @@ app.get('/'+config.uploadDir+'/*', } ); - // Have to have some sort of home page. 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) { @@ -628,6 +663,11 @@ app.use(function(err, req, res, next) { 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')); +MongoClient.connect('mongodb://localhost/crunch', function(err, database) { + if (!err) { + global.db = database; + server.listen(app.get('port'), function() { + console.log("Server listening on port " + app.get('port')); + }); + } }); diff --git a/db.js b/db.js index 26e385e..7d920bb 100644 --- a/db.js +++ b/db.js @@ -6,28 +6,6 @@ var helper = require('./helper.js'); var db = new sqlite.Database(config.dbPath); -// 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 regenerate date // Returns a epoch time in seconds exports.getLastRegenerateDate = function(cb) { @@ -107,7 +85,8 @@ exports.listCategories = function(cb) { // 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 \ + db.all('SELECT * FROM posts WHERE deleted = 0 \ + ORDER BY postDate DESC, title ASC \ LIMIT '+count+' OFFSET '+start+';', function(err, posts) { console.log(err); @@ -118,7 +97,8 @@ exports.listPosts = function(count, start, cb) { // Function to get a list of photos of a certain count and starting at an offset // Returns a list of photo objects exports.listPhotos = function(count, start, cb) { - db.all('SELECT * FROM photos ORDER BY photoDate DESC, title ASC \ + db.all('SELECT * FROM photos WHERE deleted = 0 \ + ORDER BY photoDate DESC, title ASC \ LIMIT '+count+' OFFSET '+start+';', function(err, photos) { console.log(err); diff --git a/dump/crunch/users.bson b/dump/crunch/users.bson new file mode 100644 index 0000000..90a748f Binary files /dev/null and b/dump/crunch/users.bson differ diff --git a/dump/crunch/users.metadata.json b/dump/crunch/users.metadata.json new file mode 100644 index 0000000..429ef19 --- /dev/null +++ b/dump/crunch/users.metadata.json @@ -0,0 +1 @@ +{"options":{},"indexes":[{"v":1,"key":{"_id":1},"name":"_id_","ns":"crunch.users"}]} \ No newline at end of file diff --git a/package.json b/package.json index 3bac83a..8b7314e 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,9 @@ "express-session": "^1.12.0", "gm": "^1.21.1", "jade": "^1.11.0", + "lodash": "^4.0.0", "markdown": "^0.5.0", + "mongodb": "^2.1.4", "morgan": "^1.6.1", "multer": "^1.1.0", "ncp": "^2.0.0", diff --git a/schemas.js b/schemas.js new file mode 100644 index 0000000..a8092ea --- /dev/null +++ b/schemas.js @@ -0,0 +1,10 @@ +schemas = { + user: { + username: null, + password: null, + hash: null, + email: null + } +} + +module.exports = schemas diff --git a/user.js b/user.js new file mode 100644 index 0000000..11eee60 --- /dev/null +++ b/user.js @@ -0,0 +1,72 @@ +var crypto = require('crypto'); +var schemas = require("./schemas.js"); +var _ = require("lodash"); + +var User = function(data) { + this.data = data; +} + +User.prototype.data = {} + +User.prototype.get = function (name) { + return this.data[name]; +} + +User.prototype.set = function (name, value) { + this.data[name] = value; +} + +User.prototype.sanitize = function (data) { + data = data || {}; + schema = schemas.user; + return _.pick(_.defaults(data, schema), _.keys(schema)); +} + +User.prototype.save = function (callback) { + var self = this; + this.data = this.sanitize(this.data); + db.collection("users").update({username:username},this.data, callback(err, count)); +} + +// Function to look up a user document by the users username +User.getByUsername = function (username, callback) { + console.log("Getting user document for: "+username); + db.collection("users").findOne({username:username}, function(err, doc) { + callback(null, new User(doc)); + }); +} + +// Function to verify authentication via a username and password. +// Requires internal hashing function. +User.verify = function (username, password, callback) { + console.log("Verifying user: "+username); + db.collection("users").findOne({username: username}, function(err, doc) { + if (err || !doc) { + console.log("Username "+username+" does not exist"); + console.log(err); + callback(err, null); + } + else { + console.log("Username "+username+" exists"); + console.log(username + "'s salt is " + doc.salt); + var hash = hashPassword(password, doc.salt); + console.log(username+"'s hashed password is "+hash); + db.collection("users").findOne({username: username, password: hash}, + function(err, doc) { + callback(err, doc); + }); + } + }); +} + +// Helper functions, internal to model + +hashPassword = function(password, salt) { + var hash = crypto.createHash('sha256'); + hash.update(password); + hash.update(salt); + return hash.digest('hex'); +} + + +module.exports = User; diff --git a/views/admin-sidebar.jade b/views/admin-sidebar.jade index d9f8448..ea298eb 100644 --- a/views/admin-sidebar.jade +++ b/views/admin-sidebar.jade @@ -1,5 +1,6 @@ div(class="col-sm-2 sidebar") ul(class="nav nav-sidebar") + li: a #{user.get("username")} li: a(href="/admin/") Dashboard ul(class="nav nav-sidebar") @@ -13,13 +14,13 @@ div(class="col-sm-2 sidebar") ul(class="nav nav-sidebar") li: a(href="/admin/gallery/new") New Gallery li: a(href="/admin/gallery/list") All Galleries - + ul(class="nav nav-sidebar") form(action="/admin/regenerate/", method="get") - button(class="btn btn-lg btn-success") + button(class="btn btn-lg btn-success") span(class="glyphicon glyphicon-repeat") Generate - + ul(class="nav nav-sidebar") form(action="/admin/publish/", method="get") - button(class="btn btn-lg btn-warning") + button(class="btn btn-lg btn-warning") span(class="glyphicon glyphicon-upload") Upload