Removed Search-Index and LokiJS, replaced with Mongo + Updated VueJs

This commit is contained in:
NGPixel
2016-10-14 23:31:15 -04:00
parent 99a07d342c
commit 6ea243e8d4
29 changed files with 1002 additions and 819 deletions

View File

@@ -32,8 +32,8 @@ module.exports = {
let self = this;
self._uploadsPath = path.resolve(ROOTPATH, appconfig.datadir.repo, 'uploads');
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'thumbs');
self._uploadsPath = path.resolve(ROOTPATH, appconfig.paths.repo, 'uploads');
self._uploadsThumbsPath = path.resolve(ROOTPATH, appconfig.paths.data, 'thumbs');
return self;
@@ -51,20 +51,127 @@ module.exports = {
awaitWriteFinish: true
});
//-> Add new upload file
self._watcher.on('add', (p) => {
let pInfo = lcdata.parseUploadsRelPath(p);
let pInfo = self.parseUploadsRelPath(p);
return self.processFile(pInfo.folder, pInfo.filename).then((mData) => {
ws.emit('uploadsAddFiles', {
auth: WSInternalKey,
content: mData
});
}).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) {
@@ -88,20 +195,21 @@ module.exports = {
if(s.size < 3145728) { // ignore files larger than 3MB
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 cacheThumbnailPathStr = path.format(cacheThumbnailPath);
mData = _.pick(mData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']);
mData.uid = fUid;
mData.category = 'image';
mData.mime = mimeInfo.mime;
mData.folder = fldName;
mData.filename = f;
mData.basename = fPathObj.name;
mData.filesize = s.size;
mData.uploadedOn = moment().utc();
let mData = {
_id: fUid,
category: 'image',
mime: mimeInfo.mime,
extra: _.pick(mImgData, ['format', 'width', 'height', 'density', 'hasAlpha', 'orientation']),
folder: null,
filename: f,
basename: fPathObj.name,
filesize: s.size
}
// Generate thumbnail
@@ -124,14 +232,13 @@ module.exports = {
// Other Files
return {
uid: fUid,
category: 'file',
_id: fUid,
category: 'binary',
mime: mimeInfo.mime,
folder: fldName,
filename: f,
basename: fPathObj.name,
filesize: s.size,
uploadedOn: moment().utc()
filesize: s.size
};
});

View File

@@ -45,7 +45,7 @@ module.exports = function(passport, appconfig) {
db.onReady.then(() => {
if(db.User.count() < 1) {
/*if(db.User.count() < 1) {
winston.info('No administrator account found. Creating a new one...');
if(db.User.insert({
email: appconfig.admin,
@@ -57,7 +57,7 @@ module.exports = function(passport, appconfig) {
} else {
winston.error('An error occured while creating administrator account: ');
}
}
}*/
return true;

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;
};

54
models/db/entry.js Normal file
View 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
View 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
View 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
View 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);

View File

@@ -25,8 +25,8 @@ module.exports = {
let self = this;
self._repoPath = path.resolve(ROOTPATH, appconfig.datadir.repo);
self._cachePath = path.resolve(ROOTPATH, appconfig.datadir.db, 'cache');
self._repoPath = path.resolve(ROOTPATH, appconfig.paths.repo);
self._cachePath = path.resolve(ROOTPATH, appconfig.paths.data, 'cache');
return self;
@@ -321,11 +321,13 @@ module.exports = {
text: mark.removeMarkdown(pageData.markdown)
};
}).then((content) => {
ws.emit('searchAdd', {
auth: WSInternalKey,
content
return db.Entry.create({
_id: content.entryPath,
title: content.meta.title || content.entryPath,
subtitle: content.meta.subtitle || '',
parent: content.parent.title || '',
content: content.text || ''
});
return true;
});
},

View File

@@ -43,10 +43,10 @@ module.exports = {
//-> Build repository path
if(_.isEmpty(appconfig.datadir.repo)) {
if(_.isEmpty(appconfig.paths.repo)) {
self._repo.path = path.join(ROOTPATH, 'repo');
} else {
self._repo.path = appconfig.datadir.repo;
self._repo.path = appconfig.paths.repo;
}
//-> Initialize repository
@@ -240,14 +240,16 @@ module.exports = {
/**
* Commits uploads changes.
*
* @param {String} msg The commit message
* @return {Promise} Resolve on commit success
*/
commitUploads() {
commitUploads(msg) {
let self = this;
msg = msg || "Uploads repository sync";
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; }
});
});

View File

@@ -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
View 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;
}
};

View File

@@ -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
View 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
View 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
View 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) {
}
};