Removed Redis & MongoDB dependencies in favor of Loki.js
This commit is contained in:
		
							
								
								
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.gitignore
									
									
									
									
										vendored
									
									
								
							| @@ -45,5 +45,7 @@ jspm_packages | ||||
| # Config Files | ||||
| config.yml | ||||
|  | ||||
| # App Repo | ||||
| repo | ||||
| # Data directories | ||||
| /repo | ||||
| /data | ||||
| /uploads | ||||
| @@ -22,21 +22,13 @@ host: http://localhost | ||||
| port: 80 | ||||
|  | ||||
| # ------------------------------------------------- | ||||
| # MongoDB Connection String | ||||
| # Data Directories | ||||
| # ------------------------------------------------- | ||||
| # Full explanation + examples in the documentation (https://opsstatus.readme.io/) | ||||
|  | ||||
| db: mongodb://localhost/wiki | ||||
|  | ||||
| # ------------------------------------------------- | ||||
| # Redis Connection Info | ||||
| # ------------------------------------------------- | ||||
| # Full explanation + examples in the documentation (https://opsstatus.readme.io/) | ||||
|  | ||||
| redis: | ||||
|   host: localhost | ||||
|   port: 6379 | ||||
|   db: 0 | ||||
| datadir: | ||||
|   repo: ./repo | ||||
|   db: ./data | ||||
|   uploads: ./uploads | ||||
|  | ||||
| # ------------------------------------------------- | ||||
| # Git Connection Info | ||||
|   | ||||
| @@ -2,13 +2,13 @@ var express = require('express'); | ||||
| var router = express.Router(); | ||||
| var passport = require('passport'); | ||||
| var ExpressBrute = require('express-brute'); | ||||
| var ExpressBruteRedisStore = require('express-brute-redis'); | ||||
| //var ExpressBruteRedisStore = require('express-brute-redis'); | ||||
| var moment = require('moment'); | ||||
|  | ||||
| /** | ||||
|  * Setup Express-Brute | ||||
|  */ | ||||
| var EBstore = new ExpressBruteRedisStore({ | ||||
| /*var EBstore = new ExpressBruteRedisStore({ | ||||
|     prefix: 'bf:', | ||||
|     client: red | ||||
| }); | ||||
| @@ -26,7 +26,7 @@ var bruteforce = new ExpressBrute(EBstore, { | ||||
| 	    }); | ||||
| 		res.redirect('/login'); | ||||
| 	} | ||||
| }); | ||||
| });*/ | ||||
|  | ||||
| /** | ||||
|  * Login form | ||||
| @@ -37,7 +37,7 @@ router.get('/login', function(req, res, next) { | ||||
| 	}); | ||||
| }); | ||||
|  | ||||
| router.post('/login', bruteforce.prevent, function(req, res, next) { | ||||
| router.post('/login', /*bruteforce.prevent,*/ function(req, res, next) { | ||||
| 		passport.authenticate('local', function(err, user, info) { | ||||
|  | ||||
| 			if (err) { return next(err); } | ||||
| @@ -54,9 +54,9 @@ router.post('/login', bruteforce.prevent, function(req, res, next) { | ||||
|  | ||||
| 			req.logIn(user, function(err) { | ||||
| 	      if (err) { return next(err); } | ||||
| 	      req.brute.reset(function () { | ||||
| 	      //req.brute.reset(function () { | ||||
| 				return res.redirect('/'); | ||||
| 			}); | ||||
| 			//}); | ||||
| 	    }); | ||||
|  | ||||
| 		})(req, res, next); | ||||
|   | ||||
| @@ -9,11 +9,12 @@ module.exports = function(passport, appconfig) { | ||||
|     }); | ||||
|  | ||||
|     passport.deserializeUser(function(id, done) { | ||||
|         db.User.findById(id).then((user) => { | ||||
|         let user = db.User.find({ id }); | ||||
|         if(user) { | ||||
|             done(null, user); | ||||
|         }).catch((err) => { | ||||
|         } else { | ||||
|             done(err, null); | ||||
|         }); | ||||
|         } | ||||
|     }); | ||||
|  | ||||
|     // Setup local user authentication strategy | ||||
| @@ -42,23 +43,23 @@ module.exports = function(passport, appconfig) { | ||||
|  | ||||
|     // Check for admin access | ||||
|  | ||||
|     db.connectPromise.then(() => { | ||||
|     db.onReady.then(() => { | ||||
|  | ||||
|         db.User.count().then((count) => { | ||||
|             if(count < 1) { | ||||
|                 winston.info('No administrator account found. Creating a new one...'); | ||||
|                 db.User.new({ | ||||
|                     email: appconfig.admin, | ||||
|                     firstName: "Admin", | ||||
|                     lastName: "Admin", | ||||
|                     password: "admin123" | ||||
|                 }).then(() => { | ||||
|                     winston.info('Administrator account created successfully!'); | ||||
|                 }).catch((ex) => { | ||||
|                     winston.error('An error occured while creating administrator account: ' + ex); | ||||
|                 }); | ||||
|         if(db.User.count() < 1) { | ||||
|             winston.info('No administrator account found. Creating a new one...'); | ||||
|             if(db.User.insert({ | ||||
|                 email: appconfig.admin, | ||||
|                 firstName: "Admin", | ||||
|                 lastName: "Admin", | ||||
|                 password: "admin123" | ||||
|             })) { | ||||
|                 winston.info('Administrator account created successfully!'); | ||||
|             } else { | ||||
|                 winston.error('An error occured while creating administrator account: '); | ||||
|             } | ||||
|         }); | ||||
|         } | ||||
|  | ||||
|         return true; | ||||
|  | ||||
|     }); | ||||
|  | ||||
|   | ||||
| @@ -1,158 +1,9 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var modb = require('mongoose'); | ||||
| var bcrypt = require('bcryptjs-then'); | ||||
| var Promise = require('bluebird'); | ||||
| var _ = require('lodash'); | ||||
|  | ||||
| /** | ||||
|  * User Schema | ||||
|  * | ||||
|  * @type       {Object} | ||||
|  */ | ||||
| var userSchema = modb.Schema({ | ||||
|  | ||||
|   email: { | ||||
|     type: String, | ||||
|     required: true, | ||||
|     index: true, | ||||
|     minlength: 6 | ||||
|   }, | ||||
|   password: { | ||||
|     type: String, | ||||
|     required: true | ||||
|   }, | ||||
|   firstName: { | ||||
|     type: String, | ||||
|     required: true, | ||||
|     minlength: 1 | ||||
|   }, | ||||
|   lastName: { | ||||
|     type: String, | ||||
|     required: true, | ||||
|     minlength: 1 | ||||
|   }, | ||||
|   timezone: { | ||||
|     type: String, | ||||
|     required: true, | ||||
|     default: 'UTC' | ||||
|   }, | ||||
|   lang: { | ||||
|     type: String, | ||||
|     required: true, | ||||
|     default: 'en' | ||||
|   }, | ||||
|   rights: [{ | ||||
|     type: String, | ||||
|     required: true | ||||
|   }] | ||||
|  | ||||
| }, | ||||
| { | ||||
|   timestamps: {} | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * VIRTUAL - Full Name | ||||
|  */ | ||||
| userSchema.virtual('fullName').get(function() { | ||||
|   return this.firstName + ' ' + this.lastName; | ||||
| }); | ||||
|  | ||||
| /** | ||||
|  * INSTANCE - Validate password against hash | ||||
|  * | ||||
|  * @param      {string}   uPassword  The user password | ||||
|  * @return     {Promise<Boolean>}  Promise with valid / invalid boolean | ||||
|  */ | ||||
| userSchema.methods.validatePassword = function(uPassword) { | ||||
|   let self = this; | ||||
|   return bcrypt.compare(uPassword, self.password); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * MODEL - Generate hash from password | ||||
|  * | ||||
|  * @param      {string}   uPassword  The user password | ||||
|  * @return     {Promise<String>}  Promise with generated hash | ||||
|  */ | ||||
| userSchema.statics.generateHash = function(uPassword) { | ||||
|     return bcrypt.hash(uPassword, 10); | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * MODEL - Create a new user | ||||
|  * | ||||
|  * @param      {Object}   nUserData  User data | ||||
|  * @return     {Promise}  Promise of the create operation | ||||
|  */ | ||||
| userSchema.statics.new = function(nUserData) { | ||||
|  | ||||
|   let self = this; | ||||
|  | ||||
|   return self.generateHash(nUserData.password).then((passhash) => { | ||||
|     return this.create({ | ||||
|       _id: db.ObjectId(), | ||||
|       email: nUserData.email, | ||||
|       firstName: nUserData.firstName, | ||||
|       lastName: nUserData.lastName, | ||||
|       password: passhash, | ||||
|       rights: ['admin'] | ||||
|     }); | ||||
|   }); | ||||
| module.exports = { | ||||
|  | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * MODEL - Edit a user | ||||
|  * | ||||
|  * @param      {String}   userId  The user identifier | ||||
|  * @param      {Object}   data    The user data | ||||
|  * @return     {Promise}  Promise of the update operation | ||||
|  */ | ||||
| userSchema.statics.edit = function(userId, data) { | ||||
|  | ||||
|   let self = this; | ||||
|  | ||||
|   // Change basic info | ||||
|  | ||||
|   let fdata = { | ||||
|     email: data.email, | ||||
|     firstName: data.firstName, | ||||
|     lastName: data.lastName, | ||||
|     timezone: data.timezone, | ||||
|     lang: data.lang, | ||||
|     rights: data.rights | ||||
|   }; | ||||
|   let waitTask = null; | ||||
|  | ||||
|   // Change password? | ||||
|  | ||||
|   if(!_.isEmpty(data.password) && _.trim(data.password) !== '********') { | ||||
|     waitTask = self.generateHash(data.password).then((passhash) => { | ||||
|       fdata.password = passhash; | ||||
|       return fdata; | ||||
|     }); | ||||
|   } else { | ||||
|     waitTask = Promise.resolve(fdata); | ||||
|   } | ||||
|  | ||||
|   // Update user | ||||
|  | ||||
|   return waitTask.then((udata) => { | ||||
|     return this.findByIdAndUpdate(userId, udata, { runValidators: true }); | ||||
|   }); | ||||
|  | ||||
| }; | ||||
|  | ||||
| /** | ||||
|  * MODEL - Delete a user | ||||
|  * | ||||
|  * @param      {String}   userId  The user ID | ||||
|  * @return     {Promise}  Promise of the delete operation | ||||
|  */ | ||||
| userSchema.statics.erase = function(userId) { | ||||
|   return this.findByIdAndRemove(userId); | ||||
| }; | ||||
|  | ||||
| module.exports = modb.model('User', userSchema); | ||||
							
								
								
									
										35
									
								
								models/localdata.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										35
									
								
								models/localdata.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,35 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var fs = require('fs'), | ||||
| 	_ = require('lodash'); | ||||
|  | ||||
| /** | ||||
|  * Local Data Storage | ||||
|  * | ||||
|  * @param      {Object}  appconfig  The application configuration | ||||
|  */ | ||||
| module.exports = (appconfig) => { | ||||
|  | ||||
| 	// Create DB folder | ||||
|  | ||||
| 	try { | ||||
| 		fs.mkdirSync(appconfig.datadir.db); | ||||
| 	} catch (err) { | ||||
| 		if(err.code !== 'EEXIST') { | ||||
| 			winston.error(err); | ||||
| 			process.exit(1); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| 	// Create Uploads folder | ||||
|  | ||||
| 	try { | ||||
| 		fs.mkdirSync(appconfig.datadir.uploads); | ||||
| 	} catch (err) { | ||||
| 		if(err.code !== 'EEXIST') { | ||||
| 			winston.error(err); | ||||
| 			process.exit(1); | ||||
| 		} | ||||
| 	} | ||||
|  | ||||
| }; | ||||
							
								
								
									
										60
									
								
								models/loki.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										60
									
								
								models/loki.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,60 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var loki = require('lokijs'), | ||||
| 	 fs   = require("fs"), | ||||
| 	 path = require("path"), | ||||
| 	 Promise = require('bluebird'), | ||||
| 	 _ = require('lodash'); | ||||
|  | ||||
| /** | ||||
|  * Loki.js module | ||||
|  * | ||||
|  * @param      {Object}  appconfig  Application config | ||||
|  * @return     {Object}  LokiJS instance | ||||
|  */ | ||||
| module.exports = function(appconfig) { | ||||
|  | ||||
| 	let dbReadyResolve; | ||||
| 	let dbReady = new Promise((resolve, reject) => { | ||||
| 		dbReadyResolve = resolve; | ||||
| 	}); | ||||
|  | ||||
| 	// Initialize Loki.js | ||||
|  | ||||
| 	let dbModel = { | ||||
| 		Store: new loki(path.join(appconfig.datadir.db, 'app.db'), { | ||||
| 			env: 'NODEJS', | ||||
| 			autosave: true, | ||||
| 			autosaveInterval: 5000 | ||||
| 		}), | ||||
| 		Models: {}, | ||||
| 		onReady: dbReady | ||||
| 	}; | ||||
|  | ||||
| 	// Load Models | ||||
|  | ||||
| 	let dbModelsPath = path.join(ROOTPATH, 'models/db'); | ||||
|  | ||||
| 	dbModel.Store.loadDatabase({}, () => { | ||||
|  | ||||
| 		fs | ||||
| 		.readdirSync(dbModelsPath) | ||||
| 		.filter(function(file) { | ||||
| 			return (file.indexOf(".") !== 0); | ||||
| 		}) | ||||
| 		.forEach(function(file) { | ||||
| 			let modelName = _.upperFirst(_.split(file,'.')[0]); | ||||
| 			dbModel.Models[modelName] = require(path.join(dbModelsPath, file)); | ||||
| 			dbModel[modelName] = dbModel.Store.getCollection(modelName); | ||||
| 			if(!dbModel[modelName]) { | ||||
| 				dbModel[modelName] = dbModel.Store.addCollection(modelName); | ||||
| 			} | ||||
| 		}); | ||||
|  | ||||
| 		dbReadyResolve(); | ||||
|  | ||||
| 	}); | ||||
|  | ||||
| 	return dbModel; | ||||
|  | ||||
| }; | ||||
| @@ -1,53 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var modb = require('mongoose'), | ||||
| 	 fs   = require("fs"), | ||||
| 	 path = require("path"), | ||||
| 	 _ = require('lodash'); | ||||
|  | ||||
| /** | ||||
|  * MongoDB module | ||||
|  * | ||||
|  * @param      {Object}  appconfig  Application config | ||||
|  * @return     {Object}  Mongoose instance | ||||
|  */ | ||||
| module.exports = function(appconfig) { | ||||
|  | ||||
| 	modb.Promise = require('bluebird'); | ||||
|  | ||||
| 	let dbModels = {}; | ||||
| 	let dbModelsPath = path.join(ROOTPATH, 'models/db'); | ||||
|  | ||||
| 	// Event handlers | ||||
|  | ||||
| 	modb.connection.on('error', (err) => { | ||||
| 		winston.error('Failed to connect to MongoDB instance.'); | ||||
| 	}); | ||||
| 	modb.connection.once('open', function() { | ||||
| 		winston.log('Connected to MongoDB instance.'); | ||||
| 	}); | ||||
|  | ||||
| 	// Store connection handle | ||||
|  | ||||
| 	dbModels.connection = modb.connection; | ||||
| 	dbModels.ObjectId = modb.Types.ObjectId; | ||||
|  | ||||
| 	// Load Models | ||||
|  | ||||
| 	fs | ||||
| 	.readdirSync(dbModelsPath) | ||||
| 	.filter(function(file) { | ||||
| 		return (file.indexOf(".") !== 0); | ||||
| 	}) | ||||
| 	.forEach(function(file) { | ||||
| 		let modelName = _.upperFirst(_.split(file,'.')[0]); | ||||
| 		dbModels[modelName] = require(path.join(dbModelsPath, file)); | ||||
| 	}); | ||||
|  | ||||
| 	// Connect | ||||
|  | ||||
| 	dbModels.connectPromise = modb.connect(appconfig.db); | ||||
|  | ||||
| 	return dbModels; | ||||
|  | ||||
| }; | ||||
| @@ -1,41 +0,0 @@ | ||||
| "use strict"; | ||||
|  | ||||
| var Redis = require('ioredis'), | ||||
| 	_ = require('lodash'); | ||||
|  | ||||
| /** | ||||
|  * Redis module | ||||
|  * | ||||
|  * @param      {Object}  appconfig  Application config | ||||
|  * @return     {Redis}   Redis instance | ||||
|  */ | ||||
| module.exports = (appconfig) => { | ||||
|  | ||||
| 	let rd = null; | ||||
|  | ||||
| 	if(_.isArray(appconfig.redis)) { | ||||
| 		rd = new Redis.Cluster(appconfig.redis, { | ||||
| 			scaleReads: 'master', | ||||
| 			redisOptions: { | ||||
| 				lazyConnect: false | ||||
| 			} | ||||
| 		}); | ||||
| 	} else { | ||||
| 		rd = new Redis(_.defaultsDeep(appconfig.redis), { | ||||
| 			lazyConnect: false | ||||
| 		}); | ||||
| 	} | ||||
|  | ||||
| 	// Handle connection errors | ||||
|  | ||||
| 	rd.on('error', (err) => { | ||||
| 		winston.error('Failed to connect to Redis instance(s). [err-1]'); | ||||
| 	}); | ||||
|  | ||||
| 	rd.on('node error', (err) => { | ||||
| 		winston.error('Failed to connect to Redis instance(s). [err-2]'); | ||||
| 	}); | ||||
|  | ||||
| 	return rd; | ||||
|  | ||||
| }; | ||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							| @@ -38,20 +38,20 @@ | ||||
|     "cheerio": "^0.20.0", | ||||
|     "compression": "^1.6.2", | ||||
|     "connect-flash": "^0.1.1", | ||||
|     "connect-loki": "^1.0.4", | ||||
|     "connect-redis": "^3.1.0", | ||||
|     "cookie-parser": "^1.4.3", | ||||
|     "express": "^4.14.0", | ||||
|     "express-brute": "^0.7.0-beta.0", | ||||
|     "express-brute-redis": "0.0.1", | ||||
|     "express-session": "^1.14.0", | ||||
|     "express-validator": "^2.20.8", | ||||
|     "highlight.js": "^9.6.0", | ||||
|     "i18next": "^3.4.1", | ||||
|     "i18next-express-middleware": "^1.0.1", | ||||
|     "i18next-node-fs-backend": "^0.1.2", | ||||
|     "ioredis": "^2.3.0", | ||||
|     "js-yaml": "^3.6.1", | ||||
|     "lodash": "^4.15.0", | ||||
|     "lokijs": "^1.4.1", | ||||
|     "markdown-it": "^7.0.1", | ||||
|     "markdown-it-abbr": "^1.0.3", | ||||
|     "markdown-it-anchor": "^2.5.0", | ||||
| @@ -64,8 +64,6 @@ | ||||
|     "markdown-it-toc-and-anchor": "^4.1.1", | ||||
|     "moment": "^2.14.1", | ||||
|     "moment-timezone": "^0.5.5", | ||||
|     "mongoose": "^4.5.9", | ||||
|     "mongoose-delete": "^0.3.4", | ||||
|     "nodegit": "^0.14.1", | ||||
|     "passport": "^0.3.2", | ||||
|     "passport-local": "^1.0.0", | ||||
| @@ -86,7 +84,6 @@ | ||||
|     "chai-as-promised": "^5.3.0", | ||||
|     "codacy-coverage": "^2.0.0", | ||||
|     "font-awesome": "^4.6.3", | ||||
|     "gridlex": "^2.1.1", | ||||
|     "gulp": "^3.9.1", | ||||
|     "gulp-babel": "^6.1.2", | ||||
|     "gulp-clean-css": "^2.0.12", | ||||
| @@ -99,12 +96,12 @@ | ||||
|     "gulp-tar": "^1.9.0", | ||||
|     "gulp-uglify": "^2.0.0", | ||||
|     "gulp-zip": "^3.2.0", | ||||
|     "istanbul": "^0.4.4", | ||||
|     "istanbul": "^0.4.5", | ||||
|     "jquery": "^3.1.0", | ||||
|     "merge-stream": "^1.0.0", | ||||
|     "mocha": "^3.0.2", | ||||
|     "mocha-lcov-reporter": "^1.2.0", | ||||
|     "nodemon": "^1.10.0", | ||||
|     "nodemon": "^1.10.2", | ||||
|     "snyk": "^1.18.0", | ||||
|     "vue": "^1.0.26" | ||||
|   } | ||||
|   | ||||
| @@ -14,8 +14,9 @@ global.winston = require('winston'); | ||||
| winston.info('[SERVER] Requarks Wiki is initializing...'); | ||||
|  | ||||
| var appconfig = require('./models/config')('./config.yml'); | ||||
| global.db = require('./models/mongodb')(appconfig); | ||||
| global.red = require('./models/redis')(appconfig); | ||||
| var lcdata = require('./models/localdata')(appconfig); | ||||
|  | ||||
| global.db = require('./models/loki')(appconfig); | ||||
| global.git = require('./models/git').init(appconfig); | ||||
| global.mark = require('./models/markdown'); | ||||
|  | ||||
| @@ -28,7 +29,7 @@ var express = require('express'); | ||||
| var path = require('path'); | ||||
| var favicon = require('serve-favicon'); | ||||
| var session = require('express-session'); | ||||
| var redisStore = require('connect-redis')(session); | ||||
| var lokiStore = require('connect-loki')(session); | ||||
| var cookieParser = require('cookie-parser'); | ||||
| var bodyParser = require('body-parser'); | ||||
| var flash = require('connect-flash'); | ||||
| @@ -68,7 +69,7 @@ var strategy = require('./models/auth')(passport, appconfig); | ||||
| app.use(cookieParser()); | ||||
| app.use(session({ | ||||
|   name: 'requarkswiki.sid', | ||||
|   store: new redisStore({ client: red }), | ||||
|   store: new lokiStore({ path: path.join(appconfig.datadir.db, 'sessions.db') }), | ||||
|   secret: appconfig.sessionSecret, | ||||
|   resave: false, | ||||
|   saveUninitialized: false | ||||
|   | ||||
		Reference in New Issue
	
	Block a user