mirror of https://github.com/Requarks/wiki.git
29 changed files with 995 additions and 812 deletions
Split 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