Removed Search-Index and LokiJS, replaced with Mongo + Updated VueJs
This commit is contained in:
parent
99a07d342c
commit
6ea243e8d4
@ -1,8 +1,7 @@
|
|||||||
language: node_js
|
language: node_js
|
||||||
node_js:
|
node_js:
|
||||||
- '6'
|
- '6'
|
||||||
- '5'
|
- '4'
|
||||||
- '4.4'
|
|
||||||
addons:
|
addons:
|
||||||
apt:
|
apt:
|
||||||
sources:
|
sources:
|
||||||
|
84
agent.js
84
agent.js
@ -25,18 +25,22 @@ if(!process.argv[2] || process.argv[2].length !== 40) {
|
|||||||
global.WSInternalKey = process.argv[2];
|
global.WSInternalKey = process.argv[2];
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load modules
|
// Load global modules
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
winston.info('[AGENT] Background Agent is initializing...');
|
winston.info('[AGENT] Background Agent is initializing...');
|
||||||
|
|
||||||
var appconfig = require('./models/config')('./config.yml');
|
var appconfig = require('./models/config')('./config.yml');
|
||||||
global.lcdata = require('./models/localdata').init(appconfig, 'agent');
|
global.db = require('./models/mongo').init(appconfig);
|
||||||
var upl = require('./models/uploads').init(appconfig);
|
global.upl = require('./models/agent/uploads').init(appconfig);
|
||||||
|
|
||||||
global.git = require('./models/git').init(appconfig);
|
global.git = require('./models/git').init(appconfig);
|
||||||
global.entries = require('./models/entries').init(appconfig);
|
global.entries = require('./models/entries').init(appconfig);
|
||||||
global.mark = require('./models/markdown');
|
global.mark = require('./models/markdown');
|
||||||
|
global.ws = require('socket.io-client')('http://localhost:' + appconfig.wsPort, { reconnectionAttempts: 10 });
|
||||||
|
|
||||||
|
// ----------------------------------------
|
||||||
|
// Load modules
|
||||||
|
// ----------------------------------------
|
||||||
|
|
||||||
var _ = require('lodash');
|
var _ = require('lodash');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
@ -45,10 +49,6 @@ var fs = Promise.promisifyAll(require("fs-extra"));
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var cron = require('cron').CronJob;
|
var cron = require('cron').CronJob;
|
||||||
|
|
||||||
global.ws = require('socket.io-client')('http://localhost:' + appconfig.wsPort, { reconnectionAttempts: 10 });
|
|
||||||
|
|
||||||
const mimeImgTypes = ['image/png', 'image/jpg']
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Start Cron
|
// Start Cron
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
@ -72,16 +72,17 @@ var job = new cron({
|
|||||||
// Prepare async job collector
|
// Prepare async job collector
|
||||||
|
|
||||||
let jobs = [];
|
let jobs = [];
|
||||||
let repoPath = path.resolve(ROOTPATH, appconfig.datadir.repo);
|
let repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
|
||||||
let dataPath = path.resolve(ROOTPATH, appconfig.datadir.db);
|
let dataPath = path.resolve(ROOTPATH, appconfig.paths.data);
|
||||||
let uploadsPath = path.join(repoPath, 'uploads');
|
let uploadsPath = path.join(repoPath, 'uploads');
|
||||||
|
let uploadsTempPath = path.join(dataPath, 'temp-upload');
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Compile Jobs
|
// REGULAR JOBS
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
//*****************************************
|
//*****************************************
|
||||||
//-> Resync with Git remote
|
//-> Sync with Git remote
|
||||||
//*****************************************
|
//*****************************************
|
||||||
|
|
||||||
jobs.push(git.onReady.then(() => {
|
jobs.push(git.onReady.then(() => {
|
||||||
@ -143,51 +144,30 @@ var job = new cron({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
//*****************************************
|
//*****************************************
|
||||||
//-> Refresh uploads data
|
//-> Clear failed temporary upload files
|
||||||
//*****************************************
|
//*****************************************
|
||||||
|
|
||||||
jobs.push(fs.readdirAsync(uploadsPath).then((ls) => {
|
jobs.push(
|
||||||
|
fs.readdirAsync(uploadsTempPath).then((ls) => {
|
||||||
|
|
||||||
return Promise.map(ls, (f) => {
|
let fifteenAgo = moment().subtract(15, 'minutes');
|
||||||
return fs.statAsync(path.join(uploadsPath, f)).then((s) => { return { filename: f, stat: s }; });
|
|
||||||
}).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => {
|
|
||||||
|
|
||||||
let folderNames = _.map(arrDirs, 'filename');
|
return Promise.map(ls, (f) => {
|
||||||
folderNames.unshift('');
|
return fs.statAsync(path.join(uploadsTempPath, f)).then((s) => { return { filename: f, stat: s }; });
|
||||||
|
}).filter((s) => { return s.stat.isFile(); }).then((arrFiles) => {
|
||||||
|
return Promise.map(arrFiles, (f) => {
|
||||||
|
|
||||||
ws.emit('uploadsSetFolders', {
|
if(moment(f.stat.ctime).isBefore(fifteenAgo, 'minute')) {
|
||||||
auth: WSInternalKey,
|
return fs.unlinkAsync(path.join(uploadsTempPath, f.filename));
|
||||||
content: folderNames
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
let allFiles = [];
|
})
|
||||||
|
);
|
||||||
// Travel each directory
|
|
||||||
|
|
||||||
return Promise.map(folderNames, (fldName) => {
|
|
||||||
let fldPath = path.join(uploadsPath, fldName);
|
|
||||||
return fs.readdirAsync(fldPath).then((fList) => {
|
|
||||||
return Promise.map(fList, (f) => {
|
|
||||||
return upl.processFile(fldName, f).then((mData) => {
|
|
||||||
if(mData) {
|
|
||||||
allFiles.push(mData);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, {concurrency: 3});
|
|
||||||
});
|
|
||||||
}, {concurrency: 1}).finally(() => {
|
|
||||||
|
|
||||||
ws.emit('uploadsSetFiles', {
|
|
||||||
auth: WSInternalKey,
|
|
||||||
content: allFiles
|
|
||||||
});
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
|
|
||||||
}));
|
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Run
|
// Run
|
||||||
@ -198,9 +178,11 @@ var job = new cron({
|
|||||||
|
|
||||||
if(!jobUplWatchStarted) {
|
if(!jobUplWatchStarted) {
|
||||||
jobUplWatchStarted = true;
|
jobUplWatchStarted = true;
|
||||||
upl.watch();
|
upl.initialScan();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
winston.error('[AGENT] One or more jobs have failed: ', err);
|
winston.error('[AGENT] One or more jobs have failed: ', err);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
|
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@ -104,9 +104,9 @@ class Alerts {
|
|||||||
|
|
||||||
if(nAlertIdx >= 0 && nAlert) {
|
if(nAlertIdx >= 0 && nAlert) {
|
||||||
nAlert.class += ' exit';
|
nAlert.class += ' exit';
|
||||||
self.mdl.children.$set(nAlertIdx, nAlert);
|
Vue.set(self.mdl.children, nAlertIdx, nAlert);
|
||||||
_.delay(() => {
|
_.delay(() => {
|
||||||
self.mdl.children.$remove(nAlert);
|
self.mdl.children.splice(nAlertIdx, 1);
|
||||||
}, 500);
|
}, 500);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,11 +6,6 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
|
|
||||||
$('#search-input').focus();
|
$('#search-input').focus();
|
||||||
|
|
||||||
Vue.transition('slide', {
|
|
||||||
enterClass: 'slideInDown',
|
|
||||||
leaveClass: 'fadeOutUp'
|
|
||||||
});
|
|
||||||
|
|
||||||
$('.searchresults').css('display', 'block');
|
$('.searchresults').css('display', 'block');
|
||||||
|
|
||||||
var vueHeader = new Vue({
|
var vueHeader = new Vue({
|
||||||
@ -47,8 +42,8 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
},
|
},
|
||||||
searchmoveidx: (val, oldVal) => {
|
searchmoveidx: (val, oldVal) => {
|
||||||
if(val > 0) {
|
if(val > 0) {
|
||||||
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1].document) ?
|
vueHeader.searchmovekey = (vueHeader.searchmovearr[val - 1]) ?
|
||||||
'res.' + vueHeader.searchmovearr[val - 1].document.entryPath :
|
'res.' + vueHeader.searchmovearr[val - 1]._id :
|
||||||
'sug.' + vueHeader.searchmovearr[val - 1];
|
'sug.' + vueHeader.searchmovearr[val - 1];
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchmovekey = '';
|
vueHeader.searchmovekey = '';
|
||||||
@ -66,8 +61,8 @@ jQuery( document ).ready(function( $ ) {
|
|||||||
if(vueHeader.searchmoveidx < 1) { return; }
|
if(vueHeader.searchmoveidx < 1) { return; }
|
||||||
let i = vueHeader.searchmoveidx - 1;
|
let i = vueHeader.searchmoveidx - 1;
|
||||||
|
|
||||||
if(vueHeader.searchmovearr[i].document) {
|
if(vueHeader.searchmovearr[i]) {
|
||||||
window.location.assign('/' + vueHeader.searchmovearr[i].document.entryPath);
|
window.location.assign('/' + vueHeader.searchmovearr[i]._id);
|
||||||
} else {
|
} else {
|
||||||
vueHeader.searchq = vueHeader.searchmovearr[i];
|
vueHeader.searchq = vueHeader.searchmovearr[i];
|
||||||
}
|
}
|
||||||
|
@ -34,9 +34,15 @@ wsPort: 8080
|
|||||||
# Data Directories
|
# Data Directories
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
datadir:
|
paths:
|
||||||
repo: ./repo
|
repo: ./repo
|
||||||
db: ./data
|
data: ./data
|
||||||
|
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
# Database Connection String
|
||||||
|
# ---------------------------------------------------------------------
|
||||||
|
|
||||||
|
db: mongodb://localhost:27017/wiki
|
||||||
|
|
||||||
# ---------------------------------------------------------------------
|
# ---------------------------------------------------------------------
|
||||||
# Git Connection Info
|
# Git Connection Info
|
||||||
|
@ -2,14 +2,16 @@ var express = require('express');
|
|||||||
var router = express.Router();
|
var router = express.Router();
|
||||||
var passport = require('passport');
|
var passport = require('passport');
|
||||||
var ExpressBrute = require('express-brute');
|
var ExpressBrute = require('express-brute');
|
||||||
var ExpressBruteLokiStore = require('express-brute-loki');
|
var ExpressBruteMongoStore = require('express-brute-mongo');
|
||||||
var moment = require('moment');
|
var moment = require('moment');
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Setup Express-Brute
|
* Setup Express-Brute
|
||||||
*/
|
*/
|
||||||
var EBstore = new ExpressBruteLokiStore({
|
var EBstore = new ExpressBruteMongoStore((ready) => {
|
||||||
path: './data/brute.db'
|
db.onReady.then(() => {
|
||||||
|
ready(db.connection.collection('bruteforce-store'));
|
||||||
|
});
|
||||||
});
|
});
|
||||||
var bruteforce = new ExpressBrute(EBstore, {
|
var bruteforce = new ExpressBrute(EBstore, {
|
||||||
freeRetries: 5,
|
freeRetries: 5,
|
||||||
|
@ -40,58 +40,68 @@ router.get('/t/*', (req, res, next) => {
|
|||||||
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
router.post('/img', lcdata.uploadImgHandler, (req, res, next) => {
|
||||||
|
|
||||||
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
let destFolder = _.chain(req.body.folder).trim().toLower().value();
|
||||||
let destFolderPath = lcdata.validateUploadsFolder(destFolder);
|
|
||||||
|
|
||||||
Promise.map(req.files, (f) => {
|
ws.emit('uploadsValidateFolder', {
|
||||||
|
auth: WSInternalKey,
|
||||||
|
content: destFolder
|
||||||
|
}, (destFolderPath) => {
|
||||||
|
|
||||||
|
if(!destFolderPath) {
|
||||||
|
return res.json({ ok: false, msg: 'Invalid Folder' });
|
||||||
|
}
|
||||||
|
|
||||||
let destFilename = '';
|
Promise.map(req.files, (f) => {
|
||||||
let destFilePath = '';
|
|
||||||
|
|
||||||
return lcdata.validateUploadsFilename(f.originalname, destFolder).then((fname) => {
|
let destFilename = '';
|
||||||
|
let destFilePath = '';
|
||||||
destFilename = fname;
|
|
||||||
destFilePath = path.resolve(destFolderPath, destFilename);
|
|
||||||
|
|
||||||
return readChunk(f.path, 0, 262);
|
return lcdata.validateUploadsFilename(f.originalname, destFolder).then((fname) => {
|
||||||
|
|
||||||
|
destFilename = fname;
|
||||||
|
destFilePath = path.resolve(destFolderPath, destFilename);
|
||||||
|
|
||||||
}).then((buf) => {
|
return readChunk(f.path, 0, 262);
|
||||||
|
|
||||||
//-> Check MIME type by magic number
|
}).then((buf) => {
|
||||||
|
|
||||||
let mimeInfo = fileType(buf);
|
//-> Check MIME type by magic number
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
|
||||||
return Promise.reject(new Error('Invalid file type.'));
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
|
|
||||||
}).then(() => {
|
let mimeInfo = fileType(buf);
|
||||||
|
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
//-> Move file to final destination
|
return Promise.reject(new Error('Invalid file type.'));
|
||||||
|
|
||||||
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
|
||||||
|
|
||||||
}).then(() => {
|
|
||||||
return {
|
|
||||||
ok: true,
|
|
||||||
filename: destFilename,
|
|
||||||
filesize: f.size
|
|
||||||
};
|
|
||||||
}).reflect();
|
|
||||||
|
|
||||||
}, {concurrency: 3}).then((results) => {
|
|
||||||
let uplResults = _.map(results, (r) => {
|
|
||||||
if(r.isFulfilled()) {
|
|
||||||
return r.value();
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
ok: false,
|
|
||||||
msg: r.reason().message
|
|
||||||
}
|
}
|
||||||
}
|
return true;
|
||||||
|
|
||||||
|
}).then(() => {
|
||||||
|
|
||||||
|
//-> Move file to final destination
|
||||||
|
|
||||||
|
return fs.moveAsync(f.path, destFilePath, { clobber: false });
|
||||||
|
|
||||||
|
}).then(() => {
|
||||||
|
return {
|
||||||
|
ok: true,
|
||||||
|
filename: destFilename,
|
||||||
|
filesize: f.size
|
||||||
|
};
|
||||||
|
}).reflect();
|
||||||
|
|
||||||
|
}, {concurrency: 3}).then((results) => {
|
||||||
|
let uplResults = _.map(results, (r) => {
|
||||||
|
if(r.isFulfilled()) {
|
||||||
|
return r.value();
|
||||||
|
} else {
|
||||||
|
return {
|
||||||
|
ok: false,
|
||||||
|
msg: r.reason().message
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
res.json({ ok: true, results: uplResults });
|
||||||
|
}).catch((err) => {
|
||||||
|
res.json({ ok: false, msg: err.message });
|
||||||
});
|
});
|
||||||
res.json({ ok: true, results: uplResults });
|
|
||||||
}).catch((err) => {
|
|
||||||
res.json({ ok: false, msg: err.message });
|
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
@ -78,7 +78,7 @@ var paths = {
|
|||||||
/**
|
/**
|
||||||
* TASK - Starts server in development mode
|
* TASK - Starts server in development mode
|
||||||
*/
|
*/
|
||||||
gulp.task('server', ['scripts', 'css', 'fonts'], function() {
|
gulp.task('server', ['scripts', 'css'/*, 'fonts'*/], function() {
|
||||||
nodemon({
|
nodemon({
|
||||||
script: './server',
|
script: './server',
|
||||||
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
|
ignore: ['assets/', 'client/', 'data/', 'repo/', 'tests/'],
|
||||||
|
@ -32,8 +32,8 @@ module.exports = {
|
|||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
self._uploadsPath = path.resolve(ROOTPATH, appconfig.datadir.repo, 'uploads');
|
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
||||||
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'thumbs');
|
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
||||||
@ -51,20 +51,127 @@ module.exports = {
|
|||||||
awaitWriteFinish: true
|
awaitWriteFinish: true
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//-> Add new upload file
|
||||||
|
|
||||||
self._watcher.on('add', (p) => {
|
self._watcher.on('add', (p) => {
|
||||||
|
|
||||||
let pInfo = lcdata.parseUploadsRelPath(p);
|
let pInfo = self.parseUploadsRelPath(p);
|
||||||
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
|
||||||
ws.emit('uploadsAddFiles', {
|
ws.emit('uploadsAddFiles', {
|
||||||
auth: WSInternalKey,
|
auth: WSInternalKey,
|
||||||
content: mData
|
content: mData
|
||||||
});
|
});
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
return git.commitUploads();
|
return git.commitUploads('Uploaded ' + p);
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
|
||||||
|
//-> Remove upload file
|
||||||
|
|
||||||
|
self._watcher.on('unlink', (p) => {
|
||||||
|
|
||||||
|
let pInfo = self.parseUploadsRelPath(p);
|
||||||
|
return self.deleteFile(pInfo.folder, pInfo.filename).then((uID) => {
|
||||||
|
ws.emit('uploadsRemoveFiles', {
|
||||||
|
auth: WSInternalKey,
|
||||||
|
content: uID
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
return git.commitUploads('Deleted ' + p);
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initial Uploads scan
|
||||||
|
*
|
||||||
|
* @return {Promise<Void>} Promise of the scan operation
|
||||||
|
*/
|
||||||
|
initialScan() {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
return fs.readdirAsync(self._uploadsPath).then((ls) => {
|
||||||
|
|
||||||
|
// Get all folders
|
||||||
|
|
||||||
|
return Promise.map(ls, (f) => {
|
||||||
|
return fs.statAsync(path.join(self._uploadsPath, f)).then((s) => { return { filename: f, stat: s }; });
|
||||||
|
}).filter((s) => { return s.stat.isDirectory(); }).then((arrDirs) => {
|
||||||
|
|
||||||
|
let folderNames = _.map(arrDirs, 'filename');
|
||||||
|
folderNames.unshift('');
|
||||||
|
|
||||||
|
// Add folders to DB
|
||||||
|
|
||||||
|
return db.UplFolder.remove({}).then(() => {
|
||||||
|
return db.UplFolder.insertMany(_.map(folderNames, (f) => {
|
||||||
|
return { name: f };
|
||||||
|
}));
|
||||||
|
}).then(() => {
|
||||||
|
|
||||||
|
// Travel each directory and scan files
|
||||||
|
|
||||||
|
let allFiles = [];
|
||||||
|
|
||||||
|
return Promise.map(folderNames, (fldName) => {
|
||||||
|
|
||||||
|
let fldPath = path.join(self._uploadsPath, fldName);
|
||||||
|
return fs.readdirAsync(fldPath).then((fList) => {
|
||||||
|
return Promise.map(fList, (f) => {
|
||||||
|
return upl.processFile(fldName, f).then((mData) => {
|
||||||
|
if(mData) {
|
||||||
|
allFiles.push(mData);
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
});
|
||||||
|
}, {concurrency: 3});
|
||||||
|
});
|
||||||
|
}, {concurrency: 1}).finally(() => {
|
||||||
|
|
||||||
|
// Add files to DB
|
||||||
|
|
||||||
|
return db.UplFile.remove({}).then(() => {
|
||||||
|
if(_.isArray(allFiles) && allFiles.length > 0) {
|
||||||
|
return db.UplFile.insertMany(allFiles);
|
||||||
|
} else {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
}).then(() => {
|
||||||
|
|
||||||
|
// Watch for new changes
|
||||||
|
|
||||||
|
return upl.watch();
|
||||||
|
|
||||||
|
})
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parse relative Uploads path
|
||||||
|
*
|
||||||
|
* @param {String} f Relative Uploads path
|
||||||
|
* @return {Object} Parsed path (folder and filename)
|
||||||
|
*/
|
||||||
|
parseUploadsRelPath(f) {
|
||||||
|
|
||||||
|
let fObj = path.parse(f);
|
||||||
|
return {
|
||||||
|
folder: fObj.dir,
|
||||||
|
filename: fObj.base
|
||||||
|
};
|
||||||
|
|
||||||
},
|
},
|
||||||
|
|
||||||
processFile(fldName, f) {
|
processFile(fldName, f) {
|
||||||
@ -88,20 +195,21 @@ module.exports = {
|
|||||||
|
|
||||||
if(s.size < 3145728) { // ignore files larger than 3MB
|
if(s.size < 3145728) { // ignore files larger than 3MB
|
||||||
if(_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
if(_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], mimeInfo.mime)) {
|
||||||
return self.getImageMetadata(fPath).then((mData) => {
|
return self.getImageMetadata(fPath).then((mImgData) => {
|
||||||
|
|
||||||
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'));
|
let cacheThumbnailPath = path.parse(path.join(self._uploadsThumbsPath, fUid + '.png'));
|
||||||
let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
|
let cacheThumbnailPathStr = path.format(cacheThumbnailPath);
|
||||||
|
|
||||||
mData = _.pick(mData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']);
|
let mData = {
|
||||||
mData.uid = fUid;
|
_id: fUid,
|
||||||
mData.category = 'image';
|
category: 'image',
|
||||||
mData.mime = mimeInfo.mime;
|
mime: mimeInfo.mime,
|
||||||
mData.folder = fldName;
|
extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
|
||||||
mData.filename = f;
|
folder: null,
|
||||||
mData.basename = fPathObj.name;
|
filename: f,
|
||||||
mData.filesize = s.size;
|
basename: fPathObj.name,
|
||||||
mData.uploadedOn = moment().utc();
|
filesize: s.size
|
||||||
|
}
|
||||||
|
|
||||||
// Generate thumbnail
|
// Generate thumbnail
|
||||||
|
|
||||||
@ -124,14 +232,13 @@ module.exports = {
|
|||||||
// Other Files
|
// Other Files
|
||||||
|
|
||||||
return {
|
return {
|
||||||
uid: fUid,
|
_id: fUid,
|
||||||
category: 'file',
|
category: 'binary',
|
||||||
mime: mimeInfo.mime,
|
mime: mimeInfo.mime,
|
||||||
folder: fldName,
|
folder: fldName,
|
||||||
filename: f,
|
filename: f,
|
||||||
basename: fPathObj.name,
|
basename: fPathObj.name,
|
||||||
filesize: s.size,
|
filesize: s.size
|
||||||
uploadedOn: moment().utc()
|
|
||||||
};
|
};
|
||||||
|
|
||||||
});
|
});
|
@ -45,7 +45,7 @@ module.exports = function(passport, appconfig) {
|
|||||||
|
|
||||||
db.onReady.then(() => {
|
db.onReady.then(() => {
|
||||||
|
|
||||||
if(db.User.count() < 1) {
|
/*if(db.User.count() < 1) {
|
||||||
winston.info('No administrator account found. Creating a new one...');
|
winston.info('No administrator account found. Creating a new one...');
|
||||||
if(db.User.insert({
|
if(db.User.insert({
|
||||||
email: appconfig.admin,
|
email: appconfig.admin,
|
||||||
@ -57,7 +57,7 @@ module.exports = function(passport, appconfig) {
|
|||||||
} else {
|
} else {
|
||||||
winston.error('An error occured while creating administrator account: ');
|
winston.error('An error occured while creating administrator account: ');
|
||||||
}
|
}
|
||||||
}
|
}*/
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
|
|
||||||
|
52
models/db.js
52
models/db.js
@ -1,52 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var loki = require('lokijs'),
|
|
||||||
fs = require("fs"),
|
|
||||||
path = require("path"),
|
|
||||||
Promise = require('bluebird'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
var cols = ['User', 'Entry'];
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
}),
|
|
||||||
onReady: dbReady
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load Models
|
|
||||||
|
|
||||||
dbModel.Store.loadDatabase({}, () => {
|
|
||||||
|
|
||||||
_.forEach(cols, (col) => {
|
|
||||||
dbModel[col] = dbModel.Store.getCollection(col);
|
|
||||||
if(!dbModel[col]) {
|
|
||||||
dbModel[col] = dbModel.Store.addCollection(col);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
dbReadyResolve();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
return dbModel;
|
|
||||||
|
|
||||||
};
|
|
54
models/db/entry.js
Normal file
54
models/db/entry.js
Normal file
@ -0,0 +1,54 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const modb = require('mongoose'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Entry schema
|
||||||
|
*
|
||||||
|
* @type {<Mongoose.Schema>}
|
||||||
|
*/
|
||||||
|
var entrySchema = modb.Schema({
|
||||||
|
|
||||||
|
_id: String,
|
||||||
|
|
||||||
|
title: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
minlength: 2
|
||||||
|
},
|
||||||
|
subtitle: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
parent: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
},
|
||||||
|
content: {
|
||||||
|
type: String,
|
||||||
|
default: ''
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
entrySchema.index({
|
||||||
|
_id: 'text',
|
||||||
|
title: 'text',
|
||||||
|
subtitle: 'text',
|
||||||
|
content: 'text'
|
||||||
|
}, {
|
||||||
|
weights: {
|
||||||
|
_id: 3,
|
||||||
|
title: 10,
|
||||||
|
subtitle: 5,
|
||||||
|
content: 1
|
||||||
|
},
|
||||||
|
name: 'EntriesTextIndex'
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = modb.model('Entry', entrySchema);
|
51
models/db/upl-file.js
Normal file
51
models/db/upl-file.js
Normal file
@ -0,0 +1,51 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const modb = require('mongoose'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload File schema
|
||||||
|
*
|
||||||
|
* @type {<Mongoose.Schema>}
|
||||||
|
*/
|
||||||
|
var uplFileSchema = modb.Schema({
|
||||||
|
|
||||||
|
_id: String,
|
||||||
|
|
||||||
|
category: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: 'binary'
|
||||||
|
},
|
||||||
|
mime: {
|
||||||
|
type: String,
|
||||||
|
required: true,
|
||||||
|
default: 'application/octet-stream'
|
||||||
|
},
|
||||||
|
extra: {
|
||||||
|
type: Object
|
||||||
|
},
|
||||||
|
folder: {
|
||||||
|
type: String,
|
||||||
|
ref: 'UplFolder'
|
||||||
|
},
|
||||||
|
filename: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
basename: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
},
|
||||||
|
filesize: {
|
||||||
|
type: Number,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = modb.model('UplFile', uplFileSchema);
|
23
models/db/upl-folder.js
Normal file
23
models/db/upl-folder.js
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const modb = require('mongoose'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Upload Folder schema
|
||||||
|
*
|
||||||
|
* @type {<Mongoose.Schema>}
|
||||||
|
*/
|
||||||
|
var uplFolderSchema = modb.Schema({
|
||||||
|
|
||||||
|
name: {
|
||||||
|
type: String
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = modb.model('UplFolder', uplFolderSchema);
|
24
models/db/user.js
Normal file
24
models/db/user.js
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const modb = require('mongoose'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Region schema
|
||||||
|
*
|
||||||
|
* @type {<Mongoose.Schema>}
|
||||||
|
*/
|
||||||
|
var userSchema = modb.Schema({
|
||||||
|
|
||||||
|
email: {
|
||||||
|
type: String,
|
||||||
|
required: true
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
{
|
||||||
|
timestamps: {}
|
||||||
|
});
|
||||||
|
|
||||||
|
module.exports = modb.model('User', userSchema);
|
@ -25,8 +25,8 @@ module.exports = {
|
|||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
|
|
||||||
self._repoPath = path.resolve(ROOTPATH, appconfig.datadir.repo);
|
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
|
||||||
self._cachePath = path.resolve(ROOTPATH, appconfig.datadir.db, 'cache');
|
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache');
|
||||||
|
|
||||||
return self;
|
return self;
|
||||||
|
|
||||||
@ -321,11 +321,13 @@ module.exports = {
|
|||||||
text: mark.removeMarkdown(pageData.markdown)
|
text: mark.removeMarkdown(pageData.markdown)
|
||||||
};
|
};
|
||||||
}).then((content) => {
|
}).then((content) => {
|
||||||
ws.emit('searchAdd', {
|
return db.Entry.create({
|
||||||
auth: WSInternalKey,
|
_id: content.entryPath,
|
||||||
content
|
title: content.meta.title || content.entryPath,
|
||||||
|
subtitle: content.meta.subtitle || '',
|
||||||
|
parent: content.parent.title || '',
|
||||||
|
content: content.text || ''
|
||||||
});
|
});
|
||||||
return true;
|
|
||||||
});
|
});
|
||||||
|
|
||||||
},
|
},
|
||||||
|
@ -43,10 +43,10 @@ module.exports = {
|
|||||||
|
|
||||||
//-> Build repository path
|
//-> Build repository path
|
||||||
|
|
||||||
if(_.isEmpty(appconfig.datadir.repo)) {
|
if(_.isEmpty(appconfig.paths.repo)) {
|
||||||
self._repo.path = path.join(ROOTPATH, 'repo');
|
self._repo.path = path.join(ROOTPATH, 'repo');
|
||||||
} else {
|
} else {
|
||||||
self._repo.path = appconfig.datadir.repo;
|
self._repo.path = appconfig.paths.repo;
|
||||||
}
|
}
|
||||||
|
|
||||||
//-> Initialize repository
|
//-> Initialize repository
|
||||||
@ -240,14 +240,16 @@ module.exports = {
|
|||||||
/**
|
/**
|
||||||
* Commits uploads changes.
|
* Commits uploads changes.
|
||||||
*
|
*
|
||||||
|
* @param {String} msg The commit message
|
||||||
* @return {Promise} Resolve on commit success
|
* @return {Promise} Resolve on commit success
|
||||||
*/
|
*/
|
||||||
commitUploads() {
|
commitUploads(msg) {
|
||||||
|
|
||||||
let self = this;
|
let self = this;
|
||||||
|
msg = msg || "Uploads repository sync";
|
||||||
|
|
||||||
return self._git.add('uploads').then(() => {
|
return self._git.add('uploads').then(() => {
|
||||||
return self._git.commit("Uploads repository sync").catch((err) => {
|
return self._git.commit(msg).catch((err) => {
|
||||||
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
if(_.includes(err.stdout, 'nothing to commit')) { return true; }
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@ -1,335 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var path = require('path'),
|
|
||||||
loki = require('lokijs'),
|
|
||||||
Promise = require('bluebird'),
|
|
||||||
fs = Promise.promisifyAll(require('fs-extra')),
|
|
||||||
multer = require('multer'),
|
|
||||||
_ = require('lodash');
|
|
||||||
|
|
||||||
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Local Data Storage
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application configuration
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
_uploadsPath: './repo/uploads',
|
|
||||||
_uploadsThumbsPath: './data/thumbs',
|
|
||||||
_uploadsFolders: [],
|
|
||||||
_uploadsDb: null,
|
|
||||||
|
|
||||||
uploadImgHandler: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize Local Data Storage model
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Object} Local Data Storage model instance
|
|
||||||
*/
|
|
||||||
init(appconfig, mode = 'server') {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
self._uploadsPath = path.resolve(ROOTPATH, appconfig.datadir.repo, 'uploads');
|
|
||||||
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'thumbs');
|
|
||||||
|
|
||||||
// Finish initialization tasks
|
|
||||||
|
|
||||||
switch(mode) {
|
|
||||||
case 'agent':
|
|
||||||
//todo
|
|
||||||
break;
|
|
||||||
case 'server':
|
|
||||||
self.createBaseDirectories(appconfig);
|
|
||||||
self.initMulter(appconfig);
|
|
||||||
break;
|
|
||||||
case 'ws':
|
|
||||||
self.initDb(appconfig);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
return self;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize Uploads DB
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {boolean} Void
|
|
||||||
*/
|
|
||||||
initDb(appconfig) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
let dbReadyResolve;
|
|
||||||
let dbReady = new Promise((resolve, reject) => {
|
|
||||||
dbReadyResolve = resolve;
|
|
||||||
});
|
|
||||||
|
|
||||||
// Initialize Loki.js
|
|
||||||
|
|
||||||
let dbModel = {
|
|
||||||
Store: new loki(path.join(appconfig.datadir.db, 'uploads.db'), {
|
|
||||||
env: 'NODEJS',
|
|
||||||
autosave: true,
|
|
||||||
autosaveInterval: 15000
|
|
||||||
}),
|
|
||||||
onReady: dbReady
|
|
||||||
};
|
|
||||||
|
|
||||||
// Load Models
|
|
||||||
|
|
||||||
dbModel.Store.loadDatabase({}, () => {
|
|
||||||
|
|
||||||
dbModel.Files = dbModel.Store.getCollection('Files');
|
|
||||||
if(!dbModel.Files) {
|
|
||||||
dbModel.Files = dbModel.Store.addCollection('Files', {
|
|
||||||
indices: ['category', 'folder']
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
dbReadyResolve();
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
self._uploadsDb = dbModel;
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Init Multer upload handlers
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {boolean} Void
|
|
||||||
*/
|
|
||||||
initMulter(appconfig) {
|
|
||||||
|
|
||||||
this.uploadImgHandler = multer({
|
|
||||||
storage: multer.diskStorage({
|
|
||||||
destination: (req, f, cb) => {
|
|
||||||
cb(null, path.resolve(ROOTPATH, appconfig.datadir.db, 'temp-upload'))
|
|
||||||
}
|
|
||||||
}),
|
|
||||||
fileFilter: (req, f, cb) => {
|
|
||||||
|
|
||||||
//-> Check filesize (3 MB max)
|
|
||||||
|
|
||||||
if(f.size > 3145728) {
|
|
||||||
return cb(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
//-> Check MIME type (quick check only)
|
|
||||||
|
|
||||||
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
|
||||||
return cb(null, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
cb(null, true);
|
|
||||||
}
|
|
||||||
}).array('imgfile', 20);
|
|
||||||
|
|
||||||
return true;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the thumbnails folder path.
|
|
||||||
*
|
|
||||||
* @return {String} The thumbs path.
|
|
||||||
*/
|
|
||||||
getThumbsPath() {
|
|
||||||
return this._uploadsThumbsPath;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a base directories (Synchronous).
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Void} Void
|
|
||||||
*/
|
|
||||||
createBaseDirectories(appconfig) {
|
|
||||||
|
|
||||||
winston.info('[SERVER] Checking data directories...');
|
|
||||||
|
|
||||||
try {
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db));
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db, './cache'));
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db, './thumbs'));
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.db, './temp-upload'));
|
|
||||||
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.repo));
|
|
||||||
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.datadir.repo, './uploads'));
|
|
||||||
} catch (err) {
|
|
||||||
winston.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
winston.info('[SERVER] Data and Repository directories are OK.');
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the uploads folders.
|
|
||||||
*
|
|
||||||
* @param {Array<String>} arrFolders The arr folders
|
|
||||||
* @return {Void} Void
|
|
||||||
*/
|
|
||||||
setUploadsFolders(arrFolders) {
|
|
||||||
|
|
||||||
this._uploadsFolders = arrFolders;
|
|
||||||
return;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the uploads folders.
|
|
||||||
*
|
|
||||||
* @return {Array<String>} The uploads folders.
|
|
||||||
*/
|
|
||||||
getUploadsFolders() {
|
|
||||||
return this._uploadsFolders;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates an uploads folder.
|
|
||||||
*
|
|
||||||
* @param {String} folderName The folder name
|
|
||||||
* @return {Promise} Promise of the operation
|
|
||||||
*/
|
|
||||||
createUploadsFolder(folderName) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
folderName = _.kebabCase(_.trim(folderName));
|
|
||||||
|
|
||||||
if(_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
|
||||||
return Promise.resolve(self.getUploadsFolders());
|
|
||||||
}
|
|
||||||
|
|
||||||
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
|
||||||
if(!_.includes(self._uploadsFolders, folderName)) {
|
|
||||||
self._uploadsFolders.push(folderName);
|
|
||||||
self._uploadsFolders = _.sortBy(self._uploadsFolders);
|
|
||||||
}
|
|
||||||
return self.getUploadsFolders();
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if folder is valid and exists
|
|
||||||
*
|
|
||||||
* @param {String} folderName The folder name
|
|
||||||
* @return {Boolean} True if valid
|
|
||||||
*/
|
|
||||||
validateUploadsFolder(folderName) {
|
|
||||||
|
|
||||||
folderName = (_.includes(this._uploadsFolders, folderName)) ? folderName : '';
|
|
||||||
return path.resolve(this._uploadsPath, folderName);
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Check if filename is valid and unique
|
|
||||||
*
|
|
||||||
* @param {String} f The filename
|
|
||||||
* @param {String} fld The containing folder
|
|
||||||
* @return {Promise<String>} Promise of the accepted filename
|
|
||||||
*/
|
|
||||||
validateUploadsFilename(f, fld) {
|
|
||||||
|
|
||||||
let fObj = path.parse(f);
|
|
||||||
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, '');
|
|
||||||
let fext = _.toLower(fObj.ext);
|
|
||||||
|
|
||||||
if(!_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
|
||||||
fext = '.png';
|
|
||||||
}
|
|
||||||
|
|
||||||
f = fname + fext;
|
|
||||||
let fpath = path.resolve(this._uploadsPath, fld, f);
|
|
||||||
|
|
||||||
return fs.statAsync(fpath).then((s) => {
|
|
||||||
throw new Error('File ' + f + ' already exists.');
|
|
||||||
}).catch((err) => {
|
|
||||||
if(err.code === 'ENOENT') {
|
|
||||||
return f;
|
|
||||||
}
|
|
||||||
throw err;
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Parse relative Uploads path
|
|
||||||
*
|
|
||||||
* @param {String} f Relative Uploads path
|
|
||||||
* @return {Object} Parsed path (folder and filename)
|
|
||||||
*/
|
|
||||||
parseUploadsRelPath(f) {
|
|
||||||
|
|
||||||
let fObj = path.parse(f);
|
|
||||||
return {
|
|
||||||
folder: fObj.dir,
|
|
||||||
filename: fObj.base
|
|
||||||
};
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the uploads files.
|
|
||||||
*
|
|
||||||
* @param {Array<Object>} arrFiles The uploads files
|
|
||||||
* @return {Void} Void
|
|
||||||
*/
|
|
||||||
setUploadsFiles(arrFiles) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
if(_.isArray(arrFiles) && arrFiles.length > 0) {
|
|
||||||
self._uploadsDb.Files.clear();
|
|
||||||
self._uploadsDb.Files.insert(arrFiles);
|
|
||||||
self._uploadsDb.Files.ensureIndex('category', true);
|
|
||||||
self._uploadsDb.Files.ensureIndex('folder', true);
|
|
||||||
}
|
|
||||||
|
|
||||||
return;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Adds one or more uploads files.
|
|
||||||
*
|
|
||||||
* @param {Array<Object>} arrFiles The uploads files
|
|
||||||
* @return {Void} Void
|
|
||||||
*/
|
|
||||||
addUploadsFiles(arrFiles) {
|
|
||||||
if(_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
|
||||||
this._uploadsDb.Files.insert(arrFiles);
|
|
||||||
}
|
|
||||||
return;
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Gets the uploads files.
|
|
||||||
*
|
|
||||||
* @param {String} cat Category type
|
|
||||||
* @param {String} fld Folder
|
|
||||||
* @return {Array<Object>} The files matching the query
|
|
||||||
*/
|
|
||||||
getUploadsFiles(cat, fld) {
|
|
||||||
|
|
||||||
return this._uploadsDb.Files.chain().find({
|
|
||||||
'$and': [{ 'category' : cat },{ 'folder' : fld }]
|
|
||||||
}).simplesort('filename').data();
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
64
models/mongo.js
Normal file
64
models/mongo.js
Normal file
@ -0,0 +1,64 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const modb = require('mongoose'),
|
||||||
|
fs = require("fs"),
|
||||||
|
path = require("path"),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* MongoDB module
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig Application config
|
||||||
|
* @return {Object} MongoDB wrapper instance
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize DB
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Object} DB instance
|
||||||
|
*/
|
||||||
|
init(appconfig) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
let dbModelsPath = path.resolve(ROOTPATH, 'models', 'db');
|
||||||
|
|
||||||
|
modb.Promise = require('bluebird');
|
||||||
|
|
||||||
|
// Event handlers
|
||||||
|
|
||||||
|
modb.connection.on('error', (err) => {
|
||||||
|
winston.error('[' + PROCNAME + '] Failed to connect to MongoDB instance.');
|
||||||
|
});
|
||||||
|
modb.connection.once('open', function() {
|
||||||
|
winston.log('[' + PROCNAME + '] Connected to MongoDB instance.');
|
||||||
|
});
|
||||||
|
|
||||||
|
// Store connection handle
|
||||||
|
|
||||||
|
self.connection = modb.connection;
|
||||||
|
self.ObjectId = modb.Types.ObjectId;
|
||||||
|
|
||||||
|
// Load DB Models
|
||||||
|
|
||||||
|
fs
|
||||||
|
.readdirSync(dbModelsPath)
|
||||||
|
.filter(function(file) {
|
||||||
|
return (file.indexOf(".") !== 0);
|
||||||
|
})
|
||||||
|
.forEach(function(file) {
|
||||||
|
let modelName = _.upperFirst(_.camelCase(_.split(file,'.')[0]));
|
||||||
|
self[modelName] = require(path.join(dbModelsPath, file));
|
||||||
|
});
|
||||||
|
|
||||||
|
// Connect
|
||||||
|
|
||||||
|
self.onReady = modb.connect(appconfig.db);
|
||||||
|
|
||||||
|
return self;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
198
models/search.js
198
models/search.js
@ -1,198 +0,0 @@
|
|||||||
"use strict";
|
|
||||||
|
|
||||||
var Promise = require('bluebird'),
|
|
||||||
_ = require('lodash'),
|
|
||||||
path = require('path'),
|
|
||||||
searchIndex = require('search-index'),
|
|
||||||
stopWord = require('stopword');
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Search Model
|
|
||||||
*/
|
|
||||||
module.exports = {
|
|
||||||
|
|
||||||
_si: null,
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initialize Search model
|
|
||||||
*
|
|
||||||
* @param {Object} appconfig The application config
|
|
||||||
* @return {Object} Search model instance
|
|
||||||
*/
|
|
||||||
init(appconfig) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
let dbPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'search');
|
|
||||||
|
|
||||||
searchIndex({
|
|
||||||
deletable: true,
|
|
||||||
fieldedSearch: true,
|
|
||||||
indexPath: dbPath,
|
|
||||||
logLevel: 'error',
|
|
||||||
stopwords: stopWord.getStopwords(appconfig.lang).sort()
|
|
||||||
}, (err, si) => {
|
|
||||||
if(err) {
|
|
||||||
winston.error('Failed to initialize search-index.', err);
|
|
||||||
} else {
|
|
||||||
self._si = Promise.promisifyAll(si);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return self;
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
find(terms) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
terms = _.chain(terms)
|
|
||||||
.deburr()
|
|
||||||
.toLower()
|
|
||||||
.trim()
|
|
||||||
.replace(/[^a-z0-9 ]/g, '')
|
|
||||||
.value();
|
|
||||||
|
|
||||||
let arrTerms = _.chain(terms)
|
|
||||||
.split(' ')
|
|
||||||
.filter((f) => { return !_.isEmpty(f); })
|
|
||||||
.value();
|
|
||||||
|
|
||||||
|
|
||||||
return self._si.searchAsync({
|
|
||||||
query: {
|
|
||||||
AND: [{ '*': arrTerms }]
|
|
||||||
},
|
|
||||||
pageSize: 10
|
|
||||||
}).get('hits').then((hits) => {
|
|
||||||
|
|
||||||
if(hits.length < 5) {
|
|
||||||
return self._si.matchAsync({
|
|
||||||
beginsWith: terms,
|
|
||||||
threshold: 3,
|
|
||||||
limit: 5,
|
|
||||||
type: 'simple'
|
|
||||||
}).then((matches) => {
|
|
||||||
|
|
||||||
return {
|
|
||||||
match: hits,
|
|
||||||
suggest: matches
|
|
||||||
};
|
|
||||||
|
|
||||||
});
|
|
||||||
} else {
|
|
||||||
return {
|
|
||||||
match: hits,
|
|
||||||
suggest: []
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
|
|
||||||
if(err.type === 'NotFoundError') {
|
|
||||||
return {
|
|
||||||
match: [],
|
|
||||||
suggest: []
|
|
||||||
};
|
|
||||||
} else {
|
|
||||||
winston.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Add a document to the index
|
|
||||||
*
|
|
||||||
* @param {Object} content Document content
|
|
||||||
* @return {Promise} Promise of the add operation
|
|
||||||
*/
|
|
||||||
add(content) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return self.delete(content.entryPath).then(() => {
|
|
||||||
|
|
||||||
return self._si.addAsync({
|
|
||||||
entryPath: content.entryPath,
|
|
||||||
title: content.meta.title,
|
|
||||||
subtitle: content.meta.subtitle || '',
|
|
||||||
parent: content.parent.title || '',
|
|
||||||
content: content.text || ''
|
|
||||||
}, {
|
|
||||||
fieldOptions: [{
|
|
||||||
fieldName: 'entryPath',
|
|
||||||
searchable: true,
|
|
||||||
weight: 2
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'title',
|
|
||||||
nGramLength: [1, 2],
|
|
||||||
searchable: true,
|
|
||||||
weight: 3
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'subtitle',
|
|
||||||
searchable: true,
|
|
||||||
weight: 1,
|
|
||||||
store: false
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'parent',
|
|
||||||
searchable: false,
|
|
||||||
},
|
|
||||||
{
|
|
||||||
fieldName: 'content',
|
|
||||||
searchable: true,
|
|
||||||
weight: 0,
|
|
||||||
store: false
|
|
||||||
}]
|
|
||||||
}).then(() => {
|
|
||||||
winston.info('Entry ' + content.entryPath + ' added/updated to index.');
|
|
||||||
return true;
|
|
||||||
}).catch((err) => {
|
|
||||||
winston.error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
winston.error(err);
|
|
||||||
});
|
|
||||||
|
|
||||||
},
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Delete an entry from the index
|
|
||||||
*
|
|
||||||
* @param {String} The entry path
|
|
||||||
* @return {Promise} Promise of the operation
|
|
||||||
*/
|
|
||||||
delete(entryPath) {
|
|
||||||
|
|
||||||
let self = this;
|
|
||||||
|
|
||||||
return self._si.searchAsync({
|
|
||||||
query: {
|
|
||||||
AND: [{ 'entryPath': [entryPath] }]
|
|
||||||
}
|
|
||||||
}).then((results) => {
|
|
||||||
|
|
||||||
if(results.totalHits > 0) {
|
|
||||||
let delIds = _.map(results.hits, 'id');
|
|
||||||
return self._si.delAsync(delIds);
|
|
||||||
} else {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
}).catch((err) => {
|
|
||||||
|
|
||||||
if(err.type === 'NotFoundError') {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
winston.error(err);
|
|
||||||
}
|
|
||||||
|
|
||||||
});
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
||||||
};
|
|
152
models/server/local.js
Normal file
152
models/server/local.js
Normal file
@ -0,0 +1,152 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
fs = Promise.promisifyAll(require('fs-extra')),
|
||||||
|
multer = require('multer'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Local Data Storage
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application configuration
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
_uploadsPath: './repo/uploads',
|
||||||
|
_uploadsThumbsPath: './data/thumbs',
|
||||||
|
|
||||||
|
uploadImgHandler: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Local Data Storage model
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Object} Local Data Storage model instance
|
||||||
|
*/
|
||||||
|
init(appconfig) {
|
||||||
|
|
||||||
|
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
||||||
|
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
||||||
|
|
||||||
|
this.createBaseDirectories(appconfig);
|
||||||
|
this.initMulter(appconfig);
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Init Multer upload handlers
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {boolean} Void
|
||||||
|
*/
|
||||||
|
initMulter(appconfig) {
|
||||||
|
|
||||||
|
this.uploadImgHandler = multer({
|
||||||
|
storage: multer.diskStorage({
|
||||||
|
destination: (req, f, cb) => {
|
||||||
|
cb(null, path.resolve(ROOTPATH, appconfig.paths.data, 'temp-upload'))
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
fileFilter: (req, f, cb) => {
|
||||||
|
|
||||||
|
//-> Check filesize (3 MB max)
|
||||||
|
|
||||||
|
if(f.size > 3145728) {
|
||||||
|
return cb(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
//-> Check MIME type (quick check only)
|
||||||
|
|
||||||
|
if(!_.includes(['image/png', 'image/jpeg', 'image/gif', 'image/webp'], f.mimetype)) {
|
||||||
|
return cb(null, false);
|
||||||
|
}
|
||||||
|
|
||||||
|
cb(null, true);
|
||||||
|
}
|
||||||
|
}).array('imgfile', 20);
|
||||||
|
|
||||||
|
return true;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a base directories (Synchronous).
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
|
createBaseDirectories(appconfig) {
|
||||||
|
|
||||||
|
winston.info('[SERVER] Checking data directories...');
|
||||||
|
|
||||||
|
try {
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data));
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './cache'));
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './thumbs'));
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.data, './temp-upload'));
|
||||||
|
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo));
|
||||||
|
fs.ensureDirSync(path.resolve(ROOTPATH, appconfig.paths.repo, './uploads'));
|
||||||
|
} catch (err) {
|
||||||
|
winston.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
winston.info('[SERVER] Data and Repository directories are OK.');
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the uploads path.
|
||||||
|
*
|
||||||
|
* @return {String} The uploads path.
|
||||||
|
*/
|
||||||
|
getUploadsPath() {
|
||||||
|
return this._uploadsPath;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the thumbnails folder path.
|
||||||
|
*
|
||||||
|
* @return {String} The thumbs path.
|
||||||
|
*/
|
||||||
|
getThumbsPath() {
|
||||||
|
return this._uploadsThumbsPath;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if filename is valid and unique
|
||||||
|
*
|
||||||
|
* @param {String} f The filename
|
||||||
|
* @param {String} fld The containing folder
|
||||||
|
* @return {Promise<String>} Promise of the accepted filename
|
||||||
|
*/
|
||||||
|
validateUploadsFilename(f, fld) {
|
||||||
|
|
||||||
|
let fObj = path.parse(f);
|
||||||
|
let fname = _.chain(fObj.name).trim().toLower().kebabCase().value().replace(/[^a-z0-9\-]+/g, '');
|
||||||
|
let fext = _.toLower(fObj.ext);
|
||||||
|
|
||||||
|
if(!_.includes(['.jpg', '.jpeg', '.png', '.gif', '.webp'], fext)) {
|
||||||
|
fext = '.png';
|
||||||
|
}
|
||||||
|
|
||||||
|
f = fname + fext;
|
||||||
|
let fpath = path.resolve(this._uploadsPath, fld, f);
|
||||||
|
|
||||||
|
return fs.statAsync(fpath).then((s) => {
|
||||||
|
throw new Error('File ' + f + ' already exists.');
|
||||||
|
}).catch((err) => {
|
||||||
|
if(err.code === 'ENOENT') {
|
||||||
|
return f;
|
||||||
|
}
|
||||||
|
throw err;
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
};
|
133
models/ws/search.js
Normal file
133
models/ws/search.js
Normal file
@ -0,0 +1,133 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
const Promise = require('bluebird'),
|
||||||
|
_ = require('lodash'),
|
||||||
|
path = require('path');
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Search Model
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
_si: null,
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Search model
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Object} Search model instance
|
||||||
|
*/
|
||||||
|
init(appconfig) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
return self;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
find(terms) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
terms = _.chain(terms)
|
||||||
|
.deburr()
|
||||||
|
.toLower()
|
||||||
|
.trim()
|
||||||
|
.replace(/[^a-z0-9 ]/g, '')
|
||||||
|
.split(' ')
|
||||||
|
.filter((f) => { return !_.isEmpty(f); })
|
||||||
|
.join(' ')
|
||||||
|
.value();
|
||||||
|
|
||||||
|
return db.Entry.find(
|
||||||
|
{ $text: { $search: terms } },
|
||||||
|
{ score: { $meta: "textScore" }, title: 1 }
|
||||||
|
)
|
||||||
|
.sort({ score: { $meta: "textScore" } })
|
||||||
|
.limit(10)
|
||||||
|
.exec()
|
||||||
|
.then((hits) => {
|
||||||
|
|
||||||
|
/*if(hits.length < 5) {
|
||||||
|
return self._si.matchAsync({
|
||||||
|
beginsWith: terms,
|
||||||
|
threshold: 3,
|
||||||
|
limit: 5,
|
||||||
|
type: 'simple'
|
||||||
|
}).then((matches) => {
|
||||||
|
|
||||||
|
return {
|
||||||
|
match: hits,
|
||||||
|
suggest: matches
|
||||||
|
};
|
||||||
|
|
||||||
|
});
|
||||||
|
} else {*/
|
||||||
|
return {
|
||||||
|
match: hits,
|
||||||
|
suggest: []
|
||||||
|
};
|
||||||
|
//}
|
||||||
|
|
||||||
|
}).catch((err) => {
|
||||||
|
|
||||||
|
if(err.type === 'NotFoundError') {
|
||||||
|
return {
|
||||||
|
match: [],
|
||||||
|
suggest: []
|
||||||
|
};
|
||||||
|
} else {
|
||||||
|
winston.error(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete an entry from the index
|
||||||
|
*
|
||||||
|
* @param {String} The entry path
|
||||||
|
* @return {Promise} Promise of the operation
|
||||||
|
*/
|
||||||
|
delete(entryPath) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
/*let hasResults = false;
|
||||||
|
|
||||||
|
return new Promise((resolve, reject) => {
|
||||||
|
|
||||||
|
self._si.search({
|
||||||
|
query: {
|
||||||
|
AND: { 'entryPath': [entryPath] }
|
||||||
|
}
|
||||||
|
}).on('data', (results) => {
|
||||||
|
|
||||||
|
hasResults = true;
|
||||||
|
|
||||||
|
if(results.totalHits > 0) {
|
||||||
|
let delIds = _.map(results.hits, 'id');
|
||||||
|
self._si.del(delIds).on('end', () => { return resolve(true); });
|
||||||
|
} else {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).on('error', (err) => {
|
||||||
|
|
||||||
|
if(err.type === 'NotFoundError') {
|
||||||
|
resolve(true);
|
||||||
|
} else {
|
||||||
|
winston.error(err);
|
||||||
|
reject(err);
|
||||||
|
}
|
||||||
|
|
||||||
|
}).on('end', () => {
|
||||||
|
if(!hasResults) {
|
||||||
|
resolve(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
});*/
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
160
models/ws/uploads.js
Normal file
160
models/ws/uploads.js
Normal file
@ -0,0 +1,160 @@
|
|||||||
|
"use strict";
|
||||||
|
|
||||||
|
var path = require('path'),
|
||||||
|
Promise = require('bluebird'),
|
||||||
|
fs = Promise.promisifyAll(require('fs-extra')),
|
||||||
|
multer = require('multer'),
|
||||||
|
_ = require('lodash');
|
||||||
|
|
||||||
|
var regFolderName = new RegExp("^[a-z0-9][a-z0-9\-]*[a-z0-9]$");
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uploads
|
||||||
|
*/
|
||||||
|
module.exports = {
|
||||||
|
|
||||||
|
_uploadsPath: './repo/uploads',
|
||||||
|
_uploadsThumbsPath: './data/thumbs',
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Initialize Local Data Storage model
|
||||||
|
*
|
||||||
|
* @param {Object} appconfig The application config
|
||||||
|
* @return {Object} Uploads model instance
|
||||||
|
*/
|
||||||
|
init(appconfig) {
|
||||||
|
|
||||||
|
this._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
|
||||||
|
this._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
|
||||||
|
|
||||||
|
return this;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the thumbnails folder path.
|
||||||
|
*
|
||||||
|
* @return {String} The thumbs path.
|
||||||
|
*/
|
||||||
|
getThumbsPath() {
|
||||||
|
return this._uploadsThumbsPath;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the uploads folders.
|
||||||
|
*
|
||||||
|
* @param {Array<String>} arrFolders The arr folders
|
||||||
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
|
setUploadsFolders(arrFolders) {
|
||||||
|
|
||||||
|
this._uploadsFolders = arrFolders;
|
||||||
|
return;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the uploads folders.
|
||||||
|
*
|
||||||
|
* @return {Array<String>} The uploads folders.
|
||||||
|
*/
|
||||||
|
getUploadsFolders() {
|
||||||
|
return this._uploadsFolders;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an uploads folder.
|
||||||
|
*
|
||||||
|
* @param {String} folderName The folder name
|
||||||
|
* @return {Promise} Promise of the operation
|
||||||
|
*/
|
||||||
|
createUploadsFolder(folderName) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
folderName = _.kebabCase(_.trim(folderName));
|
||||||
|
|
||||||
|
if(_.isEmpty(folderName) || !regFolderName.test(folderName)) {
|
||||||
|
return Promise.resolve(self.getUploadsFolders());
|
||||||
|
}
|
||||||
|
|
||||||
|
return fs.ensureDirAsync(path.join(self._uploadsPath, folderName)).then(() => {
|
||||||
|
if(!_.includes(self._uploadsFolders, folderName)) {
|
||||||
|
self._uploadsFolders.push(folderName);
|
||||||
|
self._uploadsFolders = _.sortBy(self._uploadsFolders);
|
||||||
|
}
|
||||||
|
return self.getUploadsFolders();
|
||||||
|
});
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if folder is valid and exists
|
||||||
|
*
|
||||||
|
* @param {String} folderName The folder name
|
||||||
|
* @return {Boolean} True if valid
|
||||||
|
*/
|
||||||
|
validateUploadsFolder(folderName) {
|
||||||
|
|
||||||
|
if(_.includes(this._uploadsFolders, folderName)) {
|
||||||
|
return path.resolve(this._uploadsPath, folderName);
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the uploads files.
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} arrFiles The uploads files
|
||||||
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
|
setUploadsFiles(arrFiles) {
|
||||||
|
|
||||||
|
let self = this;
|
||||||
|
|
||||||
|
/*if(_.isArray(arrFiles) && arrFiles.length > 0) {
|
||||||
|
self._uploadsDb.Files.clear();
|
||||||
|
self._uploadsDb.Files.insert(arrFiles);
|
||||||
|
self._uploadsDb.Files.ensureIndex('category', true);
|
||||||
|
self._uploadsDb.Files.ensureIndex('folder', true);
|
||||||
|
}*/
|
||||||
|
|
||||||
|
return;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds one or more uploads files.
|
||||||
|
*
|
||||||
|
* @param {Array<Object>} arrFiles The uploads files
|
||||||
|
* @return {Void} Void
|
||||||
|
*/
|
||||||
|
addUploadsFiles(arrFiles) {
|
||||||
|
if(_.isArray(arrFiles) || _.isPlainObject(arrFiles)) {
|
||||||
|
//this._uploadsDb.Files.insert(arrFiles);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
},
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the uploads files.
|
||||||
|
*
|
||||||
|
* @param {String} cat Category type
|
||||||
|
* @param {String} fld Folder
|
||||||
|
* @return {Array<Object>} The files matching the query
|
||||||
|
*/
|
||||||
|
getUploadsFiles(cat, fld) {
|
||||||
|
|
||||||
|
return /*this._uploadsDb.Files.chain().find({
|
||||||
|
'$and': [{ 'category' : cat },{ 'folder' : fld }]
|
||||||
|
}).simplesort('filename').data()*/;
|
||||||
|
|
||||||
|
},
|
||||||
|
|
||||||
|
deleteUploadsFile(fldName, f) {
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
};
|
31
package.json
31
package.json
@ -29,26 +29,24 @@
|
|||||||
},
|
},
|
||||||
"homepage": "https://github.com/Requarks/wiki#readme",
|
"homepage": "https://github.com/Requarks/wiki#readme",
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=4.4.5"
|
"node": ">=4.6"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"auto-load": "^2.1.0",
|
"auto-load": "^2.1.0",
|
||||||
"bcryptjs-then": "^1.0.1",
|
"bcryptjs-then": "^1.0.1",
|
||||||
"bluebird": "^3.4.6",
|
"bluebird": "^3.4.6",
|
||||||
"body-parser": "^1.15.2",
|
"body-parser": "^1.15.2",
|
||||||
"bson": "^0.5.5",
|
|
||||||
"cheerio": "^0.22.0",
|
"cheerio": "^0.22.0",
|
||||||
"child-process-promise": "^2.1.3",
|
"child-process-promise": "^2.1.3",
|
||||||
"chokidar": "^1.6.0",
|
"chokidar": "^1.6.0",
|
||||||
"compression": "^1.6.2",
|
"compression": "^1.6.2",
|
||||||
"connect-flash": "^0.1.1",
|
"connect-flash": "^0.1.1",
|
||||||
"connect-loki": "^1.0.6",
|
"connect-mongo": "^1.3.2",
|
||||||
"connect-redis": "^3.1.0",
|
|
||||||
"cookie-parser": "^1.4.3",
|
"cookie-parser": "^1.4.3",
|
||||||
"cron": "^1.1.0",
|
"cron": "^1.1.1",
|
||||||
"express": "^4.14.0",
|
"express": "^4.14.0",
|
||||||
"express-brute": "^1.0.0",
|
"express-brute": "^1.0.0",
|
||||||
"express-brute-loki": "^1.0.0",
|
"express-brute-mongo": "^0.1.0",
|
||||||
"express-session": "^1.14.1",
|
"express-session": "^1.14.1",
|
||||||
"express-validator": "^2.20.10",
|
"express-validator": "^2.20.10",
|
||||||
"farmhash": "^1.2.1",
|
"farmhash": "^1.2.1",
|
||||||
@ -61,31 +59,30 @@
|
|||||||
"i18next-express-middleware": "^1.0.2",
|
"i18next-express-middleware": "^1.0.2",
|
||||||
"i18next-node-fs-backend": "^0.1.2",
|
"i18next-node-fs-backend": "^0.1.2",
|
||||||
"js-yaml": "^3.6.1",
|
"js-yaml": "^3.6.1",
|
||||||
"lodash": "^4.16.2",
|
"lodash": "^4.16.4",
|
||||||
"lokijs": "^1.4.1",
|
|
||||||
"markdown-it": "^8.0.0",
|
"markdown-it": "^8.0.0",
|
||||||
"markdown-it-abbr": "^1.0.4",
|
"markdown-it-abbr": "^1.0.4",
|
||||||
"markdown-it-anchor": "^2.5.0",
|
"markdown-it-anchor": "^2.5.0",
|
||||||
"markdown-it-attrs": "^0.7.1",
|
"markdown-it-attrs": "^0.7.1",
|
||||||
"markdown-it-emoji": "^1.2.0",
|
"markdown-it-emoji": "^1.3.0",
|
||||||
"markdown-it-expand-tabs": "^1.0.11",
|
"markdown-it-expand-tabs": "^1.0.11",
|
||||||
"markdown-it-external-links": "0.0.5",
|
"markdown-it-external-links": "0.0.6",
|
||||||
"markdown-it-footnote": "^3.0.1",
|
"markdown-it-footnote": "^3.0.1",
|
||||||
"markdown-it-task-lists": "^1.4.1",
|
"markdown-it-task-lists": "^1.4.1",
|
||||||
"moment": "^2.15.1",
|
"moment": "^2.15.1",
|
||||||
"moment-timezone": "^0.5.5",
|
"moment-timezone": "^0.5.6",
|
||||||
|
"mongoose": "^4.6.3",
|
||||||
"multer": "^1.2.0",
|
"multer": "^1.2.0",
|
||||||
"passport": "^0.3.2",
|
"passport": "^0.3.2",
|
||||||
"passport-local": "^1.0.0",
|
"passport-local": "^1.0.0",
|
||||||
"pug": "^2.0.0-beta6",
|
"pug": "^2.0.0-beta6",
|
||||||
"read-chunk": "^2.0.0",
|
"read-chunk": "^2.0.0",
|
||||||
"remove-markdown": "^0.1.0",
|
"remove-markdown": "^0.1.0",
|
||||||
"search-index": "^0.8.15",
|
|
||||||
"serve-favicon": "^2.3.0",
|
"serve-favicon": "^2.3.0",
|
||||||
"sharp": "^0.16.0",
|
"sharp": "^0.16.0",
|
||||||
"simplemde": "^1.11.2",
|
"simplemde": "^1.11.2",
|
||||||
"snyk": "^1.19.1",
|
"snyk": "^1.19.1",
|
||||||
"socket.io": "^1.4.8",
|
"socket.io": "^1.5.0",
|
||||||
"sticky-js": "^1.0.7",
|
"sticky-js": "^1.0.7",
|
||||||
"validator": "^6.0.0",
|
"validator": "^6.0.0",
|
||||||
"validator-as-promised": "^1.0.2",
|
"validator-as-promised": "^1.0.2",
|
||||||
@ -96,7 +93,7 @@
|
|||||||
"babel-preset-es2015": "^6.16.0",
|
"babel-preset-es2015": "^6.16.0",
|
||||||
"bulma": "^0.1.2",
|
"bulma": "^0.1.2",
|
||||||
"chai": "^3.5.0",
|
"chai": "^3.5.0",
|
||||||
"chai-as-promised": "^5.3.0",
|
"chai-as-promised": "^6.0.0",
|
||||||
"codacy-coverage": "^2.0.0",
|
"codacy-coverage": "^2.0.0",
|
||||||
"filesize.js": "^1.0.1",
|
"filesize.js": "^1.0.1",
|
||||||
"font-awesome": "^4.6.3",
|
"font-awesome": "^4.6.3",
|
||||||
@ -120,10 +117,10 @@
|
|||||||
"merge-stream": "^1.0.0",
|
"merge-stream": "^1.0.0",
|
||||||
"mocha": "^3.1.0",
|
"mocha": "^3.1.0",
|
||||||
"mocha-lcov-reporter": "^1.2.0",
|
"mocha-lcov-reporter": "^1.2.0",
|
||||||
"nodemon": "^1.10.2",
|
"nodemon": "^1.11.0",
|
||||||
"sticky-js": "^1.1.0",
|
"sticky-js": "^1.1.2",
|
||||||
"twemoji-awesome": "^1.0.4",
|
"twemoji-awesome": "^1.0.4",
|
||||||
"vue": "^1.0.28"
|
"vue": "^2.0.1"
|
||||||
},
|
},
|
||||||
"snyk": true
|
"snyk": true
|
||||||
}
|
}
|
||||||
|
11
server.js
11
server.js
@ -20,8 +20,8 @@ winston.info('[SERVER] Requarks Wiki is initializing...');
|
|||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
|
|
||||||
var appconfig = require('./models/config')('./config.yml');
|
var appconfig = require('./models/config')('./config.yml');
|
||||||
global.lcdata = require('./models/localdata').init(appconfig, 'server');
|
global.lcdata = require('./models/server/local').init(appconfig);
|
||||||
global.db = require('./models/db')(appconfig);
|
global.db = require('./models/mongo').init(appconfig);
|
||||||
global.git = require('./models/git').init(appconfig, false);
|
global.git = require('./models/git').init(appconfig, false);
|
||||||
global.entries = require('./models/entries').init(appconfig);
|
global.entries = require('./models/entries').init(appconfig);
|
||||||
global.mark = require('./models/markdown');
|
global.mark = require('./models/markdown');
|
||||||
@ -35,7 +35,7 @@ var express = require('express');
|
|||||||
var path = require('path');
|
var path = require('path');
|
||||||
var favicon = require('serve-favicon');
|
var favicon = require('serve-favicon');
|
||||||
var session = require('express-session');
|
var session = require('express-session');
|
||||||
var lokiStore = require('connect-loki')(session);
|
const mongoStore = require('connect-mongo')(session);
|
||||||
var cookieParser = require('cookie-parser');
|
var cookieParser = require('cookie-parser');
|
||||||
var bodyParser = require('body-parser');
|
var bodyParser = require('body-parser');
|
||||||
var flash = require('connect-flash');
|
var flash = require('connect-flash');
|
||||||
@ -73,7 +73,10 @@ var strategy = require('./models/auth')(passport, appconfig);
|
|||||||
app.use(cookieParser());
|
app.use(cookieParser());
|
||||||
app.use(session({
|
app.use(session({
|
||||||
name: 'requarkswiki.sid',
|
name: 'requarkswiki.sid',
|
||||||
store: new lokiStore({ path: path.join(appconfig.datadir.db, 'sessions.db') }),
|
store: new mongoStore({
|
||||||
|
mongooseConnection: db.connection,
|
||||||
|
touchAfter: 15
|
||||||
|
}),
|
||||||
secret: appconfig.sessionSecret,
|
secret: appconfig.sessionSecret,
|
||||||
resave: false,
|
resave: false,
|
||||||
saveUninitialized: false
|
saveUninitialized: false
|
||||||
|
@ -20,19 +20,20 @@
|
|||||||
block rootNavRight
|
block rootNavRight
|
||||||
i.nav-item#notifload
|
i.nav-item#notifload
|
||||||
|
|
||||||
.box.searchresults.animated(v-show='searchactive', transition='slide', v-cloak, style={'display':'none'})
|
transition(name="searchresults-anim", enter-active-class="slideInDown", leave-active-class="fadeOutUp")
|
||||||
.menu
|
.box.searchresults.animated(v-show='searchactive', v-cloak, style={'display':'none'})
|
||||||
p.menu-label
|
.menu
|
||||||
| Search Results
|
p.menu-label
|
||||||
ul.menu-list
|
| Search Results
|
||||||
li(v-if="searchres.length === 0")
|
ul.menu-list
|
||||||
a: em No results matching your query
|
li(v-if="searchres.length === 0")
|
||||||
li(v-for='sres in searchres')
|
a: em No results matching your query
|
||||||
a(href='/{{ sres.document.entryPath }}', v-bind:class="{ 'is-active': searchmovekey === 'res.' + sres.document.entryPath }") {{ sres.document.title }}
|
li(v-for='sres in searchres')
|
||||||
p.menu-label(v-if='searchsuggest.length > 0')
|
a(v-bind:href="'/' + sres._id", v-bind:class="{ 'is-active': searchmovekey === 'res.' + sres._id }") {{ sres.title }}
|
||||||
| Did you mean...?
|
p.menu-label(v-if='searchsuggest.length > 0')
|
||||||
ul.menu-list(v-if='searchsuggest.length > 0')
|
| Did you mean...?
|
||||||
li(v-for='sug in searchsuggest')
|
ul.menu-list(v-if='searchsuggest.length > 0')
|
||||||
a(v-on:click="useSuggestion(sug)", v-bind:class="{ 'is-active': searchmovekey === 'sug.' + sug }") {{ sug }}
|
li(v-for='sug in searchsuggest')
|
||||||
|
a(v-on:click="useSuggestion(sug)", v-bind:class="{ 'is-active': searchmovekey === 'sug.' + sug }") {{ sug }}
|
||||||
|
|
||||||
|
|
||||||
|
46
ws-server.js
46
ws-server.js
@ -31,11 +31,11 @@ global.internalAuth = require('./lib/internalAuth').init(process.argv[2]);;
|
|||||||
winston.info('[WS] WS Server is initializing...');
|
winston.info('[WS] WS Server is initializing...');
|
||||||
|
|
||||||
var appconfig = require('./models/config')('./config.yml');
|
var appconfig = require('./models/config')('./config.yml');
|
||||||
let lcdata = require('./models/localdata').init(appconfig, 'ws');
|
global.db = require('./models/mongo').init(appconfig);
|
||||||
|
global.upl = require('./models/ws/uploads').init(appconfig);
|
||||||
global.entries = require('./models/entries').init(appconfig);
|
global.entries = require('./models/entries').init(appconfig);
|
||||||
global.mark = require('./models/markdown');
|
global.mark = require('./models/markdown');
|
||||||
global.search = require('./models/search').init(appconfig);
|
global.search = require('./models/ws/search').init(appconfig);
|
||||||
|
|
||||||
// ----------------------------------------
|
// ----------------------------------------
|
||||||
// Load local modules
|
// Load local modules
|
||||||
@ -108,14 +108,14 @@ io.on('connection', (socket) => {
|
|||||||
});
|
});
|
||||||
|
|
||||||
socket.on('searchDel', (data, cb) => {
|
socket.on('searchDel', (data, cb) => {
|
||||||
cb = cb || _.noop
|
cb = cb || _.noop;
|
||||||
if(internalAuth.validateKey(data.auth)) {
|
if(internalAuth.validateKey(data.auth)) {
|
||||||
search.delete(data.entryPath);
|
search.delete(data.entryPath);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('search', (data, cb) => {
|
socket.on('search', (data, cb) => {
|
||||||
cb = cb || _.noop
|
cb = cb || _.noop;
|
||||||
search.find(data.terms).then((results) => {
|
search.find(data.terms).then((results) => {
|
||||||
cb(results);
|
cb(results);
|
||||||
});
|
});
|
||||||
@ -125,44 +125,46 @@ io.on('connection', (socket) => {
|
|||||||
// UPLOADS
|
// UPLOADS
|
||||||
//-----------------------------------------
|
//-----------------------------------------
|
||||||
|
|
||||||
socket.on('uploadsSetFolders', (data, cb) => {
|
socket.on('uploadsSetFolders', (data) => {
|
||||||
cb = cb || _.noop
|
|
||||||
if(internalAuth.validateKey(data.auth)) {
|
if(internalAuth.validateKey(data.auth)) {
|
||||||
lcdata.setUploadsFolders(data.content);
|
upl.setUploadsFolders(data.content);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('uploadsGetFolders', (data, cb) => {
|
socket.on('uploadsGetFolders', (data, cb) => {
|
||||||
cb = cb || _.noop
|
cb = cb || _.noop;
|
||||||
cb(lcdata.getUploadsFolders());
|
cb(upl.getUploadsFolders());
|
||||||
|
});
|
||||||
|
|
||||||
|
socket.on('uploadsValidateFolder', (data, cb) => {
|
||||||
|
cb = cb || _.noop;
|
||||||
|
if(internalAuth.validateKey(data.auth)) {
|
||||||
|
cb(upl.validateUploadsFolder(data.content));
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('uploadsCreateFolder', (data, cb) => {
|
socket.on('uploadsCreateFolder', (data, cb) => {
|
||||||
cb = cb || _.noop
|
cb = cb || _.noop;
|
||||||
lcdata.createUploadsFolder(data.foldername).then((fldList) => {
|
upl.createUploadsFolder(data.foldername).then((fldList) => {
|
||||||
cb(fldList);
|
cb(fldList);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('uploadsSetFiles', (data, cb) => {
|
socket.on('uploadsSetFiles', (data) => {
|
||||||
cb = cb || _.noop;
|
|
||||||
if(internalAuth.validateKey(data.auth)) {
|
if(internalAuth.validateKey(data.auth)) {
|
||||||
lcdata.setUploadsFiles(data.content);
|
upl.setUploadsFiles(data.content);
|
||||||
cb(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('uploadsAddFiles', (data, cb) => {
|
socket.on('uploadsAddFiles', (data) => {
|
||||||
cb = cb || _.noop
|
|
||||||
if(internalAuth.validateKey(data.auth)) {
|
if(internalAuth.validateKey(data.auth)) {
|
||||||
lcdata.addUploadsFiles(data.content);
|
upl.addUploadsFiles(data.content);
|
||||||
cb(true);
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
socket.on('uploadsGetImages', (data, cb) => {
|
socket.on('uploadsGetImages', (data, cb) => {
|
||||||
cb = cb || _.noop
|
cb = cb || _.noop;
|
||||||
cb(lcdata.getUploadsFiles('image', data.folder));
|
cb(upl.getUploadsFiles('image', data.folder));
|
||||||
});
|
});
|
||||||
|
|
||||||
});
|
});
|
||||||
|
Loading…
Reference in New Issue
Block a user