mirror of https://github.com/Requarks/wiki.git
29 changed files with 995 additions and 812 deletions
Unified View
Diff Options
-
3.travis.yml
-
80agent.js
-
2assets/js/app.js
-
67assets/js/libs.js
-
4client/js/components/alerts.js
-
13client/js/components/search.js
-
10config.sample.yml
-
8controllers/auth.js
-
84controllers/uploads.js
-
2gulpfile.js
-
143models/agent/uploads.js
-
4models/auth.js
-
52models/db.js
-
54models/db/entry.js
-
51models/db/upl-file.js
-
23models/db/upl-folder.js
-
24models/db/user.js
-
14models/entries.js
-
10models/git.js
-
335models/localdata.js
-
64models/mongo.js
-
198models/search.js
-
152models/server/local.js
-
133models/ws/search.js
-
160models/ws/uploads.js
-
31package.json
-
11server.js
-
29views/common/header.pug
-
46ws-server.js
2
assets/js/app.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
67
assets/js/libs.js
File diff suppressed because it is too large
View File
File diff suppressed because it is too large
View File
@ -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; |
|
||||
|
|
||||
}; |
|
@ -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); |
@ -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); |
@ -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); |
@ -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); |
@ -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(); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
}; |
|
@ -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; |
||||
|
|
||||
|
} |
||||
|
|
||||
|
}; |
@ -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); |
|
||||
} |
|
||||
|
|
||||
}); |
|
||||
|
|
||||
} |
|
||||
|
|
||||
}; |
|
@ -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; |
||||
|
}); |
||||
|
|
||||
|
}, |
||||
|
|
||||
|
}; |
@ -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); |
||||
|
} |
||||
|
}); |
||||
|
|
||||
|
});*/ |
||||
|
|
||||
|
} |
||||
|
|
||||
|
}; |
@ -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) { |
||||
|
|
||||
|
} |
||||
|
|
||||
|
}; |
Write
Preview
Loading…
Cancel
Save