diff --git a/.gitignore b/.gitignore index 6ec55ce7..7e2e8edb 100644 --- a/.gitignore +++ b/.gitignore @@ -45,5 +45,7 @@ jspm_packages # Config Files config.yml -# App Repo -repo \ No newline at end of file +# Data directories +/repo +/data +/uploads \ No newline at end of file diff --git a/config.sample.yml b/config.sample.yml index a47a2d57..92b80f07 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -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 diff --git a/controllers/auth.js b/controllers/auth.js index 02e111cf..ef3932f0 100644 --- a/controllers/auth.js +++ b/controllers/auth.js @@ -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); diff --git a/models/auth.js b/models/auth.js index bae38974..a74344d0 100644 --- a/models/auth.js +++ b/models/auth.js @@ -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; }); diff --git a/models/db/user.js b/models/db/user.js index 25c93b7d..a05e795f 100644 --- a/models/db/user.js +++ b/models/db/user.js @@ -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({ +module.exports = { - 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} 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} 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'] - }); - }); - -}; - -/** - * 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); \ No newline at end of file +}; \ No newline at end of file diff --git a/models/localdata.js b/models/localdata.js new file mode 100644 index 00000000..b70f3105 --- /dev/null +++ b/models/localdata.js @@ -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); + } + } + +}; \ No newline at end of file diff --git a/models/loki.js b/models/loki.js new file mode 100644 index 00000000..9aee27b2 --- /dev/null +++ b/models/loki.js @@ -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; + +}; \ No newline at end of file diff --git a/models/mongodb.js b/models/mongodb.js deleted file mode 100644 index 46c5fa33..00000000 --- a/models/mongodb.js +++ /dev/null @@ -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; - -}; \ No newline at end of file diff --git a/models/redis.js b/models/redis.js deleted file mode 100644 index 1cad1fa9..00000000 --- a/models/redis.js +++ /dev/null @@ -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; - -}; \ No newline at end of file diff --git a/package.json b/package.json index c78922fd..f734fb51 100644 --- a/package.json +++ b/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" } diff --git a/server.js b/server.js index 36d9031f..425e29b5 100644 --- a/server.js +++ b/server.js @@ -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