Properly modeling users ( #5 ), moving over to mongodb.

This commit is contained in:
Andrew Davidson 2016-01-16 16:40:58 -05:00
parent 3dad352bba
commit 3004e5e326
9 changed files with 197 additions and 89 deletions

2
.gitignore vendored
View file

@ -13,3 +13,5 @@ node_modules
preload.sql preload.sql
dev.db dev.db
tmp/

162
app.js
View file

@ -8,8 +8,14 @@ var multer = require('multer');
var fs = require('fs'); var fs = require('fs');
var markdown = require( "markdown" ).markdown; 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 helper = require('./helper.js');
var db = require('./db.js'); var database = require('./db.js');
var genPosts = require('./genPosts.js'); var genPosts = require('./genPosts.js');
var genStatic = require('./genStatic.js'); var genStatic = require('./genStatic.js');
var genPhotos = require('./genPhotos.js'); var genPhotos = require('./genPhotos.js');
@ -33,8 +39,11 @@ app.use(flash());
app.use(require('morgan')('dev')); app.use(require('morgan')('dev'));
app.use(require('cookie-parser')()); app.use(require('cookie-parser')());
app.use(require('body-parser').urlencoded({ extended: true })); app.use(require('body-parser').urlencoded({ extended: true }));
app.use(require('express-session')({ secret: 'amdasdfasdfamd', app.use(require('express-session')({
resave: true, saveUninitialized: true })); secret: 'amdasdfasdfamd',
resave: true,
saveUninitialized: true })
);
app.use(express.static(path.join(__dirname, 'public'))); app.use(express.static(path.join(__dirname, 'public')));
var upload = multer({ dest: config.uploadDir }); var upload = multer({ dest: config.uploadDir });
@ -42,7 +51,8 @@ var upload = multer({ dest: config.uploadDir });
// Setup authentication via twitter. // Setup authentication via twitter.
passport.use(new Strategy( passport.use(new Strategy(
function(username, password, done) { 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); } if (!user) { return done(null, false); }
return done(null, user); return done(null, user);
}); });
@ -50,11 +60,13 @@ passport.use(new Strategy(
)); ));
passport.serializeUser(function(user, done) { passport.serializeUser(function(user, done) {
done(null, user.id); console.log("Serializing user: "+user.username);
done(null, user.username);
}); });
passport.deserializeUser(function(id, done) { passport.deserializeUser(function(username, done) {
db.getUserById(id, function (user) { console.log("Deserializing user: "+username);
User.getByUsername(username, function (err, user) {
if (!user) { return done(false); } if (!user) { return done(false); }
done(null, user); done(null, user);
}); });
@ -63,8 +75,8 @@ passport.deserializeUser(function(id, done) {
app.use(passport.initialize()); app.use(passport.initialize());
app.use(passport.session()); app.use(passport.session());
// Require logins for all admin pages. // Require logins for all admin pages other pages have to be handled separately.
app.all('/admin/*', require('connect-ensure-login').ensureLoggedIn()); app.all('/admin*', require('connect-ensure-login').ensureLoggedIn());
// User management routing // User management routing
app.get('/login', function(req, res) { app.get('/login', function(req, res) {
@ -78,7 +90,8 @@ app.post('/login',
app.get('/logout', function(req, res) { app.get('/logout', function(req, res) {
req.logout(); req.logout();
res.redirect('/'); res.redirect('/');
}); }
);
// Post management routing // Post management routing
app.get('/admin/post/list/:start?', app.get('/admin/post/list/:start?',
@ -89,7 +102,7 @@ app.get('/admin/post/list/:start?',
} else { } else {
var start = 0; var start = 0;
} }
db.listPosts(count, start, function(posts){ database.listPosts(count, start, function(posts){
for (post in posts) { for (post in posts) {
var date = new Date(posts[post].postDate); var date = new Date(posts[post].postDate);
posts[post].dateString = date.getFullYear() + '-' + posts[post].dateString = date.getFullYear() + '-' +
@ -98,12 +111,13 @@ app.get('/admin/post/list/:start?',
} }
res.render('admin-post-list', {posts, user: req.user}); res.render('admin-post-list', {posts, user: req.user});
}); });
}); }
);
app.get('/admin/post/view/:id?', app.get('/admin/post/view/:id?',
function (req, res, next) { function (req, res, next) {
if (req.params.id) { if (req.params.id) {
db.getPostById(req.params.id, function(row) { database.getPostById(req.params.id, function(row) {
res.render('admin-post-view', { res.render('admin-post-view', {
successNotice: req.flash('successNotice'), successNotice: req.flash('successNotice'),
failureNotice: req.flash('failureNotice'), failureNotice: req.flash('failureNotice'),
@ -122,10 +136,11 @@ app.get('/admin/post/view/:id?',
app.get('/admin/post/new', app.get('/admin/post/new',
function(req, res, next) { function(req, res, next) {
db.listCategories(function(rows){ database.listCategories(function(rows){
res.render('admin-post-new', { categories: rows, user: req.user }); res.render('admin-post-new', { categories: rows, user: req.user });
}); });
}); }
);
app.post('/admin/post/new', app.post('/admin/post/new',
function(req, res, next) { function(req, res, next) {
@ -146,36 +161,37 @@ app.post('/admin/post/new',
var categoryName = req.body.category; var categoryName = req.body.category;
var tags = helper.parseTags(req.body.tags); var tags = helper.parseTags(req.body.tags);
db.createPost(title, slug, markdown, postDate, updatedDate, createDate, function(err, row) { database.createPost(title, slug, markdown, postDate, updatedDate, createDate, function(err, row) {
db.setPostCategory(row.id, categoryName, function(category) { database.setPostCategory(row.id, categoryName, function(category) {
db.tagPost(row.id, tags); database.tagPost(row.id, tags);
req.flash('successNotice', 'Post created.'); req.flash('successNotice', 'Post created.');
res.redirect('/admin/post/edit/'+row.id); res.redirect('/admin/post/edit/'+row.id);
}); });
}); });
}); }
);
app.get('/admin/post/edit/:id', app.get('/admin/post/edit/:id',
function(req, res, next) { function(req, res, next) {
var id = req.params.id; var id = req.params.id;
async.parallel({ async.parallel({
categories: function(callback) { categories: function(callback) {
db.listCategories(function(categories) { database.listCategories(function(categories) {
callback(null, categories); callback(null, categories);
}) })
}, },
postCategory: function(callback) { postCategory: function(callback) {
db.getPostCategory(id, function(category) { database.getPostCategory(id, function(category) {
callback(null, category); callback(null, category);
}) })
}, },
postTags: function(callback) { postTags: function(callback) {
db.getPostTagsAsString(id, function(tagString) { database.getPostTagsAsString(id, function(tagString) {
callback(null, tagString); callback(null, tagString);
}) })
}, },
post: function(callback) { post: function(callback) {
db.getPostById(id, function(post) { database.getPostById(id, function(post) {
callback(null, post); callback(null, post);
}) })
} }
@ -197,7 +213,8 @@ app.get('/admin/post/edit/:id',
user: req.user user: req.user
}); });
}); });
}); }
);
app.post('/admin/post/edit/:id', app.post('/admin/post/edit/:id',
function(req, res, next) { function(req, res, next) {
@ -209,14 +226,15 @@ app.post('/admin/post/edit/:id',
var categoryName = req.body.category; var categoryName = req.body.category;
var tags = helper.parseTags(req.body.tags); var tags = helper.parseTags(req.body.tags);
console.log('Post '+id+' update request received'); console.log('Post '+id+' update request received');
db.tagPost(id, tags); database.tagPost(id, tags);
db.updatePost(id, title, slug, markdown, postDate, function(err, row) { database.updatePost(id, title, slug, markdown, postDate, function(err, row) {
db.setPostCategory(id, categoryName, function(err) { database.setPostCategory(id, categoryName, function(err) {
req.flash('successNotice', 'Post updated.'); req.flash('successNotice', 'Post updated.');
res.redirect('/admin/post/edit/'+id); res.redirect('/admin/post/edit/'+id);
}); });
}); });
}); }
);
app.get('/admin/post/regenerate/:id?', app.get('/admin/post/regenerate/:id?',
function(req, res, next) { function(req, res, next) {
@ -237,7 +255,8 @@ app.get('/admin/post/regenerate/:id?',
res.redirect('/admin/post/list'); res.redirect('/admin/post/list');
} }
}); }
);
// Photo management Routing // Photo management Routing
app.get('/admin/photo/list/:start?', app.get('/admin/photo/list/:start?',
@ -248,7 +267,7 @@ app.get('/admin/photo/list/:start?',
} else { } else {
var start = 0; var start = 0;
} }
db.listPhotos(count, start, function(photos){ database.listPhotos(count, start, function(photos){
for (photo in photos) { for (photo in photos) {
var date = new Date(photos[photo].photoDate); var date = new Date(photos[photo].photoDate);
photos[photo].dateString = date.getFullYear() + '-' + photos[photo].dateString = date.getFullYear() + '-' +
@ -257,12 +276,13 @@ app.get('/admin/photo/list/:start?',
} }
res.render('admin-photo-list', {photos, user: req.user}); res.render('admin-photo-list', {photos, user: req.user});
}); });
}); }
);
app.get('/admin/photo/view/:id?', app.get('/admin/photo/view/:id?',
function (req, res, next) { function (req, res, next) {
if (req.params.id) { if (req.params.id) {
db.getPhotoById(req.params.id, function(row) { database.getPhotoById(req.params.id, function(row) {
res.render('admin-photo-view', { res.render('admin-photo-view', {
successNotice: req.flash('successNotice'), successNotice: req.flash('successNotice'),
failureNotice: req.flash('failureNotice'), failureNotice: req.flash('failureNotice'),
@ -275,13 +295,14 @@ app.get('/admin/photo/view/:id?',
else { else {
res.redirect('/admin/photo/list'); res.redirect('/admin/photo/list');
} }
} }
); );
app.get('/admin/photo/new', app.get('/admin/photo/new',
function(req, res, next) { function(req, res, next) {
res.render('admin-photo-new', { user: req.user }); res.render('admin-photo-new', { user: req.user });
}) }
);
app.post('/admin/photo/new', app.post('/admin/photo/new',
upload.single('photo'), upload.single('photo'),
@ -318,26 +339,27 @@ app.post('/admin/photo/new',
var categoryName = req.body.category; var categoryName = req.body.category;
var path = req.file.path; 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); if (err) console.log(err);
console.log(row); console.log(row);
db.tagPhoto(row.id, tags); database.tagPhoto(row.id, tags);
req.flash('successNotice', 'Photo created.'); req.flash('successNotice', 'Photo created.');
res.redirect('/admin/photo/view/'+row.id); res.redirect('/admin/photo/view/'+row.id);
}); });
}); }
);
app.get('/admin/photo/edit/:id', app.get('/admin/photo/edit/:id',
function(req, res, next) { function(req, res, next) {
var id = req.params.id; var id = req.params.id;
async.parallel({ async.parallel({
photoTags: function(callback) { photoTags: function(callback) {
db.getPhotoTagsAsString(id, function(tagString) { database.getPhotoTagsAsString(id, function(tagString) {
callback(null, tagString); callback(null, tagString);
}) })
}, },
photo: function(callback) { photo: function(callback) {
db.getPhotoById(id, function(photo) { database.getPhotoById(id, function(photo) {
callback(null, photo); callback(null, photo);
}) })
} }
@ -353,7 +375,8 @@ app.get('/admin/photo/edit/:id',
user: req.user user: req.user
}); });
}); });
}); }
);
app.post('/admin/photo/edit/:id', app.post('/admin/photo/edit/:id',
function(req, res, next) { function(req, res, next) {
@ -364,65 +387,76 @@ app.post('/admin/photo/edit/:id',
var photoDate = helper.dateToEpoch(new Date(req.body.photoDate)); var photoDate = helper.dateToEpoch(new Date(req.body.photoDate));
var tags = helper.parseTags(req.body.tags); var tags = helper.parseTags(req.body.tags);
console.log('Post '+id+' update request received'); console.log('Post '+id+' update request received');
db.tagPhoto(id, tags); database.tagPhoto(id, tags);
db.updatePhoto(id, title, slug, markdown, photoDate, function(err, row) { database.updatePhoto(id, title, slug, markdown, photoDate, function(err, row) {
req.flash('successNotice', 'Photo updated.'); req.flash('successNotice', 'Photo updated.');
res.redirect('/admin/photo/view/'+id); res.redirect('/admin/photo/view/'+id);
}); });
}); }
);
app.get('/admin/photo/regenerate/:id', app.get('/admin/photo/regenerate/:id',
function(req, res, next) { function(req, res, next) {
console.log('Generating resized images for: '+req.params.id); 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) { genPhotos.generatePhotoSizesById(req.params.id, function(err) {
if (!err) { if (!err) {
req.flash('successNotice', 'Resized images created.'); genPhotos.generatePhotoPage(req.params.id, function(err) {
res.redirect('/admin/photo/view/'+req.params.id); 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 { else {
req.flash('failureNotice', 'Photo was unable to be resized.'); req.flash('failureNotice', 'Photo was unable to be resized.');
res.redirect('/admin/photo/view/'+req.params.id); res.redirect('/admin/photo/view/'+req.params.id);
} }
}); });
}); }
);
// Admin dashboard page. // Admin dashboard page.
app.get('/admin', app.get('/admin',
function(req, res, next) { function(req, res, next) {
async.parallel({ async.parallel({
categories: function(callback) { categories: function(callback) {
db.countCategories(function(count) { database.countCategories(function(count) {
callback(null, count); callback(null, count);
}); });
}, },
posts: function(callback) { posts: function(callback) {
db.countPosts(function(count) { database.countPosts(function(count) {
callback(null, count); callback(null, count);
}); });
}, },
galleries: function(callback) { galleries: function(callback) {
db.countGalleries(function(count) { database.countGalleries(function(count) {
callback(null, count); callback(null, count);
}); });
}, },
photos: function(callback) { photos: function(callback) {
db.countPhotos(function(count) { database.countPhotos(function(count) {
callback(null, count); callback(null, count);
}); });
}, },
tags: function(callback) { tags: function(callback) {
db.countTags(function(count) { database.countTags(function(count) {
callback(null, count); callback(null, count);
}); });
}, },
regenerateDate: function(callback) { regenerateDate: function(callback) {
db.getLastRegenerateDate(function(date) { database.getLastRegenerateDate(function(date) {
var dateString = helper.epochToDateString(date.date).slice(0,-12); var dateString = helper.epochToDateString(date.date).slice(0,-12);
callback(null, dateString); callback(null, dateString);
}); });
}, },
uploadDate: function(callback) { uploadDate: function(callback) {
db.getLastUploadDate(function(date) { database.getLastUploadDate(function(date) {
var dateString = helper.epochToDateString(date.date).slice(0,-12); var dateString = helper.epochToDateString(date.date).slice(0,-12);
callback(null, dateString); callback(null, dateString);
}); });
@ -442,7 +476,8 @@ app.get('/admin',
user: req.user}); user: req.user});
} }
); );
}); }
);
// Routes for previewing sent versions of the built items. // Routes for previewing sent versions of the built items.
app.get('/blog/*', app.get('/blog/*',
@ -470,7 +505,7 @@ app.get('/blog/*',
} }
}); });
} }
} }
); );
app.get('/photos/*', app.get('/photos/*',
@ -526,7 +561,7 @@ app.get('/galleries/*',
} }
}); });
} }
} }
); );
app.get('/static/*', app.get('/static/*',
@ -554,7 +589,7 @@ app.get('/static/*',
} }
}); });
} }
} }
); );
app.get('/'+config.uploadDir+'/*', app.get('/'+config.uploadDir+'/*',
@ -585,11 +620,11 @@ app.get('/'+config.uploadDir+'/*',
} }
); );
// Have to have some sort of home page. // Have to have some sort of home page.
app.get('/', function(req, res, next) { app.get('/', function(req, res, next) {
res.render('admin-index', { title: 'AMDavidson.com', user: req.user }); res.render('admin-index', { title: 'AMDavidson.com', user: req.user });
}); }
);
// catch 404 and forward to error handler // catch 404 and forward to error handler
app.use(function(req, res, next) { app.use(function(req, res, next) {
@ -628,6 +663,11 @@ app.use(function(err, req, res, next) {
app.set('port', process.env.PORT || 3000) app.set('port', process.env.PORT || 3000)
var server = require('http').createServer(app) var server = require('http').createServer(app)
server.listen(app.get('port'), function() { MongoClient.connect('mongodb://localhost/crunch', function(err, database) {
console.log("Server listening on port " + app.get('port')); if (!err) {
global.db = database;
server.listen(app.get('port'), function() {
console.log("Server listening on port " + app.get('port'));
});
}
}); });

28
db.js
View file

@ -6,28 +6,6 @@ var helper = require('./helper.js');
var db = new sqlite.Database(config.dbPath); 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 // Function to get the latest regenerate date
// Returns a epoch time in seconds // Returns a epoch time in seconds
exports.getLastRegenerateDate = function(cb) { 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 // Function to get a list of posts of a certain count and starting at an offset
// Returns a list of post objects // Returns a list of post objects
exports.listPosts = function(count, start, cb) { 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+';', LIMIT '+count+' OFFSET '+start+';',
function(err, posts) { function(err, posts) {
console.log(err); 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 // Function to get a list of photos of a certain count and starting at an offset
// Returns a list of photo objects // Returns a list of photo objects
exports.listPhotos = function(count, start, cb) { 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+';', LIMIT '+count+' OFFSET '+start+';',
function(err, photos) { function(err, photos) {
console.log(err); console.log(err);

BIN
dump/crunch/users.bson Normal file

Binary file not shown.

View file

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

View file

@ -13,7 +13,9 @@
"express-session": "^1.12.0", "express-session": "^1.12.0",
"gm": "^1.21.1", "gm": "^1.21.1",
"jade": "^1.11.0", "jade": "^1.11.0",
"lodash": "^4.0.0",
"markdown": "^0.5.0", "markdown": "^0.5.0",
"mongodb": "^2.1.4",
"morgan": "^1.6.1", "morgan": "^1.6.1",
"multer": "^1.1.0", "multer": "^1.1.0",
"ncp": "^2.0.0", "ncp": "^2.0.0",

10
schemas.js Normal file
View file

@ -0,0 +1,10 @@
schemas = {
user: {
username: null,
password: null,
hash: null,
email: null
}
}
module.exports = schemas

72
user.js Normal file
View file

@ -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;

View file

@ -1,5 +1,6 @@
div(class="col-sm-2 sidebar") div(class="col-sm-2 sidebar")
ul(class="nav nav-sidebar") ul(class="nav nav-sidebar")
li: a #{user.get("username")}
li: a(href="/admin/") Dashboard li: a(href="/admin/") Dashboard
ul(class="nav nav-sidebar") ul(class="nav nav-sidebar")
@ -13,13 +14,13 @@ div(class="col-sm-2 sidebar")
ul(class="nav nav-sidebar") ul(class="nav nav-sidebar")
li: a(href="/admin/gallery/new") New Gallery li: a(href="/admin/gallery/new") New Gallery
li: a(href="/admin/gallery/list") All Galleries li: a(href="/admin/gallery/list") All Galleries
ul(class="nav nav-sidebar") ul(class="nav nav-sidebar")
form(action="/admin/regenerate/", method="get") 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 span(class="glyphicon glyphicon-repeat") Generate
ul(class="nav nav-sidebar") ul(class="nav nav-sidebar")
form(action="/admin/publish/", method="get") 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 span(class="glyphicon glyphicon-upload") Upload