WebSocket server + Search index + indexable content parser
This commit is contained in:
parent
528fab6c87
commit
7945d024ad
62
agent.js
62
agent.js
@ -6,11 +6,36 @@
|
||||
|
||||
global.ROOTPATH = __dirname;
|
||||
|
||||
// ----------------------------------------
|
||||
// Load Winston
|
||||
// ----------------------------------------
|
||||
|
||||
var _isDebug = process.env.NODE_ENV === 'development';
|
||||
|
||||
global.winston = require('winston');
|
||||
winston.remove(winston.transports.Console)
|
||||
winston.add(winston.transports.Console, {
|
||||
level: (_isDebug) ? 'info' : 'warn',
|
||||
prettyPrint: true,
|
||||
colorize: true,
|
||||
silent: false,
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// Fetch internal handshake key
|
||||
// ----------------------------------------
|
||||
|
||||
if(!process.argv[2] || process.argv[2].length !== 40) {
|
||||
winston.error('[WS] Illegal process start. Missing handshake key.');
|
||||
process.exit(1);
|
||||
}
|
||||
global.WSInternalKey = process.argv[2];
|
||||
|
||||
// ----------------------------------------
|
||||
// Load modules
|
||||
// ----------------------------------------
|
||||
|
||||
global.winston = require('winston');
|
||||
winston.info('[AGENT] Background Agent is initializing...');
|
||||
|
||||
var appconfig = require('./models/config')('./config.yml');
|
||||
@ -18,7 +43,6 @@ var appconfig = require('./models/config')('./config.yml');
|
||||
global.git = require('./models/git').init(appconfig);
|
||||
global.entries = require('./models/entries').init(appconfig);
|
||||
global.mark = require('./models/markdown');
|
||||
global.search = require('./models/search').init(appconfig);
|
||||
|
||||
var _ = require('lodash');
|
||||
var moment = require('moment');
|
||||
@ -26,6 +50,8 @@ var Promise = require('bluebird');
|
||||
var fs = Promise.promisifyAll(require("fs-extra"));
|
||||
var path = require('path');
|
||||
var cron = require('cron').CronJob;
|
||||
var wsClient = require('socket.io-client');
|
||||
global.ws = wsClient('http://localhost:' + appconfig.wsPort, { reconnectionAttempts: 10 });
|
||||
|
||||
// ----------------------------------------
|
||||
// Start Cron
|
||||
@ -90,8 +116,11 @@ var job = new cron({
|
||||
//-> Update search index
|
||||
|
||||
if(fileStatus !== 'active') {
|
||||
return entries.fetchTextVersion(entryPath).then((content) => {
|
||||
console.log(content);
|
||||
return entries.fetchIndexableVersion(entryPath).then((content) => {
|
||||
ws.emit('searchAdd', {
|
||||
auth: WSInternalKey,
|
||||
content
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@ -114,25 +143,42 @@ var job = new cron({
|
||||
// ----------------------------------------
|
||||
|
||||
Promise.all(jobs).then(() => {
|
||||
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now... [' + moment().toISOString() + ']');
|
||||
winston.info('[AGENT] All jobs completed successfully! Going to sleep for now...');
|
||||
}).catch((err) => {
|
||||
winston.error('[AGENT] One or more jobs have failed [' + moment().toISOString() + ']: ', err);
|
||||
winston.error('[AGENT] One or more jobs have failed: ', err);
|
||||
}).finally(() => {
|
||||
jobIsBusy = false;
|
||||
});
|
||||
|
||||
},
|
||||
start: true,
|
||||
start: false,
|
||||
timeZone: 'UTC',
|
||||
runOnInit: true
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// Connect to local WebSocket server
|
||||
// ----------------------------------------
|
||||
|
||||
ws.on('connect', function () {
|
||||
job.start();
|
||||
winston.info('[AGENT] Background Agent started successfully! [RUNNING]');
|
||||
});
|
||||
|
||||
ws.on('connect_error', function () {
|
||||
winston.warn('[AGENT] Unable to connect to WebSocket server! Retrying...');
|
||||
});
|
||||
ws.on('reconnect_failed', function () {
|
||||
winston.error('[AGENT] Failed to reconnect to WebSocket server too many times! Stopping agent...');
|
||||
process.exit(1);
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// Shutdown gracefully
|
||||
// ----------------------------------------
|
||||
|
||||
process.on('disconnect', () => {
|
||||
winston.warn('[AGENT] Lost connection to main server. Exiting... [' + moment().toISOString() + ']');
|
||||
winston.warn('[AGENT] Lost connection to main server. Exiting...');
|
||||
job.stop();
|
||||
process.exit();
|
||||
});
|
||||
|
File diff suppressed because one or more lines are too long
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
@ -1 +1 @@
|
||||
"use strict";function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function setInputSelection(e,t,a){if(e.focus(),"undefined"!=typeof e.selectionStart)e.selectionStart=t,e.selectionEnd=a;else if(document.selection&&document.selection.createRange){e.select();var n=document.selection.createRange();n.collapse(!0),n.moveEnd("character",a),n.moveStart("character",t),n.select()}}function makeSafePath(e){var t=_.split(_.trim(e),"/");return t=_.map(t,function(e){return _.kebabCase(_.deburr(_.trim(e)))}),_.join(_.filter(t,function(e){return!_.isEmpty(e)}),"/")}var _createClass=function(){function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,a,n){return a&&e(t.prototype,a),n&&e(t,n),t}}(),Alerts=function(){function e(){_classCallCheck(this,e);var t=this;t.mdl=new Vue({el:"#alerts",data:{children:[]},methods:{acknowledge:function(e){t.close(e)}}}),t.uidNext=1}return _createClass(e,[{key:"push",value:function(e){var t=this,a=_.defaults(e,{_uid:t.uidNext,class:"is-info",message:"---",sticky:!1,title:"---"});t.mdl.children.push(a),a.sticky||_.delay(function(){t.close(a._uid)},5e3),t.uidNext++}},{key:"pushError",value:function(e,t){this.push({class:"is-danger",message:t,sticky:!1,title:e})}},{key:"pushSuccess",value:function(e,t){this.push({class:"is-success",message:t,sticky:!1,title:e})}},{key:"close",value:function(e){var t=this,a=_.findIndex(t.mdl.children,["_uid",e]),n=_.nth(t.mdl.children,a);a>=0&&n&&(n.class+=" exit",t.mdl.children.$set(a,n),_.delay(function(){t.mdl.children.$remove(n)},500))}}]),e}();jQuery(document).ready(function(e){e("a").smoothScroll({speed:400,offset:-20});new Sticky(".stickyscroll");e(window).bind("beforeunload",function(){e("#notifload").addClass("active")}),e(document).ajaxSend(function(){e("#notifload").addClass("active")}).ajaxComplete(function(){e("#notifload").removeClass("active")});var t=new Alerts;if(alertsData&&_.forEach(alertsData,function(e){t.push(e)}),1===e("#mk-editor").length)var a=new SimpleMDE({autofocus:!0,autoDownloadFontAwesome:!1,element:e("#mk-editor").get(0),hideIcons:["heading","quote"],placeholder:"Enter Markdown formatted content here...",showIcons:["strikethrough","heading-1","heading-2","heading-3","code","table","horizontal-rule"],spellChecker:!1,status:!1});if(e("#page-type-view").length&&!function(){var t="home"!==e("#page-type-view").data("entrypath")?e("#page-type-view").data("entrypath")+"/":"",a=t+"new-page";e(".btn-create-prompt").on("click",function(n){e("#txt-create-prompt").val(a),e("#modal-create-prompt").toggleClass("is-active"),setInputSelection(e("#txt-create-prompt").get(0),t.length,a.length),e("#txt-create-prompt").removeClass("is-danger").next().addClass("is-hidden")}),e("#txt-create-prompt").on("keypress",function(t){13===t.which&&e(".btn-create-go").trigger("click")}),e(".btn-create-go").on("click",function(t){var a=makeSafePath(e("#txt-create-prompt").val());_.isEmpty(a)?e("#txt-create-prompt").addClass("is-danger").next().removeClass("is-hidden"):(e("#txt-create-prompt").parent().addClass("is-loading"),window.location.assign("/create/"+a))})}(),e("#page-type-create").length&&(e(".btn-create-discard").on("click",function(t){e("#modal-create-discard").toggleClass("is-active")}),e(".btn-create-save").on("click",function(n){e.ajax(window.location.href,{data:{markdown:a.value()},dataType:"json",method:"PUT"}).then(function(a,n,o){a.ok?window.location.assign("/"+e("#page-type-create").data("entrypath")):t.pushError("Something went wrong",a.error)},function(e,a,n){t.pushError("Something went wrong","Save operation failed.")})})),e("#page-type-edit").length){e(".editor-toolbar").attr("data-margin-top",e("#header").height());new Sticky(".editor-toolbar");e(".btn-edit-discard").on("click",function(t){e("#modal-edit-discard").toggleClass("is-active")}),e(".btn-edit-save").on("click",function(n){e.ajax(window.location.href,{data:{markdown:a.value()},dataType:"json",method:"PUT"}).then(function(a,n,o){a.ok?window.location.assign("/"+e("#page-type-edit").data("entrypath")):t.pushError("Something went wrong",a.error)},function(e,a,n){t.pushError("Something went wrong","Save operation failed.")})})}if(e("#page-type-source").length){var n=ace.edit("source-display");n.setTheme("ace/theme/tomorrow_night"),n.getSession().setMode("ace/mode/markdown"),n.setReadOnly(!0),n.renderer.updateFull()}});
|
||||
"use strict";function _classCallCheck(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function setInputSelection(e,t,a){if(e.focus(),"undefined"!=typeof e.selectionStart)e.selectionStart=t,e.selectionEnd=a;else if(document.selection&&document.selection.createRange){e.select();var n=document.selection.createRange();n.collapse(!0),n.moveEnd("character",a),n.moveStart("character",t),n.select()}}function makeSafePath(e){var t=_.split(_.trim(e),"/");return t=_.map(t,function(e){return _.kebabCase(_.deburr(_.trim(e)))}),_.join(_.filter(t,function(e){return!_.isEmpty(e)}),"/")}var _createClass=function(){function e(e,t){for(var a=0;a<t.length;a++){var n=t[a];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(t,a,n){return a&&e(t.prototype,a),n&&e(t,n),t}}(),Alerts=function(){function e(){_classCallCheck(this,e);var t=this;t.mdl=new Vue({el:"#alerts",data:{children:[]},methods:{acknowledge:function(e){t.close(e)}}}),t.uidNext=1}return _createClass(e,[{key:"push",value:function(e){var t=this,a=_.defaults(e,{_uid:t.uidNext,class:"is-info",message:"---",sticky:!1,title:"---"});t.mdl.children.push(a),a.sticky||_.delay(function(){t.close(a._uid)},5e3),t.uidNext++}},{key:"pushError",value:function(e,t){this.push({class:"is-danger",message:t,sticky:!1,title:e})}},{key:"pushSuccess",value:function(e,t){this.push({class:"is-success",message:t,sticky:!1,title:e})}},{key:"close",value:function(e){var t=this,a=_.findIndex(t.mdl.children,["_uid",e]),n=_.nth(t.mdl.children,a);a>=0&&n&&(n.class+=" exit",t.mdl.children.$set(a,n),_.delay(function(){t.mdl.children.$remove(n)},500))}}]),e}();jQuery(document).ready(function(e){e("a").smoothScroll({speed:400,offset:-20});new Sticky(".stickyscroll");e(window).bind("beforeunload",function(){e("#notifload").addClass("active")}),e(document).ajaxSend(function(){e("#notifload").addClass("active")}).ajaxComplete(function(){e("#notifload").removeClass("active")});var t=new Alerts;if(alertsData&&_.forEach(alertsData,function(e){t.push(e)}),1===e("#mk-editor").length)var a=new SimpleMDE({autofocus:!0,autoDownloadFontAwesome:!1,element:e("#mk-editor").get(0),hideIcons:["heading","quote"],placeholder:"Enter Markdown formatted content here...",showIcons:["strikethrough","heading-1","heading-2","heading-3","code","table","horizontal-rule"],spellChecker:!1,status:!1});var n=io(ioHost),o=new Vue({el:"#header-container",data:{searchq:"",searchres:[]},watch:{searchq:function(e,t){e.length>=3&&n.emit("search",{terms:e},function(e){o.$set("searchres",e)})}},methods:{}});if(e("#page-type-view").length&&!function(){var t="home"!==e("#page-type-view").data("entrypath")?e("#page-type-view").data("entrypath")+"/":"",a=t+"new-page";e(".btn-create-prompt").on("click",function(n){e("#txt-create-prompt").val(a),e("#modal-create-prompt").toggleClass("is-active"),setInputSelection(e("#txt-create-prompt").get(0),t.length,a.length),e("#txt-create-prompt").removeClass("is-danger").next().addClass("is-hidden")}),e("#txt-create-prompt").on("keypress",function(t){13===t.which&&e(".btn-create-go").trigger("click")}),e(".btn-create-go").on("click",function(t){var a=makeSafePath(e("#txt-create-prompt").val());_.isEmpty(a)?e("#txt-create-prompt").addClass("is-danger").next().removeClass("is-hidden"):(e("#txt-create-prompt").parent().addClass("is-loading"),window.location.assign("/create/"+a))})}(),e("#page-type-create").length&&(e(".btn-create-discard").on("click",function(t){e("#modal-create-discard").toggleClass("is-active")}),e(".btn-create-save").on("click",function(n){e.ajax(window.location.href,{data:{markdown:a.value()},dataType:"json",method:"PUT"}).then(function(a,n,o){a.ok?window.location.assign("/"+e("#page-type-create").data("entrypath")):t.pushError("Something went wrong",a.error)},function(e,a,n){t.pushError("Something went wrong","Save operation failed.")})})),e("#page-type-edit").length){e(".editor-toolbar").attr("data-margin-top",e("#header").height());new Sticky(".editor-toolbar");e(".btn-edit-discard").on("click",function(t){e("#modal-edit-discard").toggleClass("is-active")}),e(".btn-edit-save").on("click",function(n){e.ajax(window.location.href,{data:{markdown:a.value()},dataType:"json",method:"PUT"}).then(function(a,n,o){a.ok?window.location.assign("/"+e("#page-type-edit").data("entrypath")):t.pushError("Something went wrong",a.error)},function(e,a,n){t.pushError("Something went wrong","Save operation failed.")})})}if(e("#page-type-source").length){var i=ace.edit("source-display");i.setTheme("ace/theme/tomorrow_night"),i.getSession().setMode("ace/mode/markdown"),i.setReadOnly(!0),i.renderer.updateFull()}});
|
File diff suppressed because one or more lines are too long
@ -52,6 +52,32 @@ jQuery( document ).ready(function( $ ) {
|
||||
|
||||
}
|
||||
|
||||
// ====================================
|
||||
// Establish WebSocket connection
|
||||
// ====================================
|
||||
|
||||
var socket = io(ioHost);
|
||||
|
||||
var vueHeader = new Vue({
|
||||
el: '#header-container',
|
||||
data: {
|
||||
searchq: '',
|
||||
searchres: []
|
||||
},
|
||||
watch: {
|
||||
searchq: (val, oldVal) => {
|
||||
if(val.length >= 3) {
|
||||
socket.emit('search', { terms: val }, (data) => {
|
||||
vueHeader.$set('searchres', data);
|
||||
});
|
||||
}
|
||||
}
|
||||
},
|
||||
methods: {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
// ====================================
|
||||
// Pages logic
|
||||
// ====================================
|
||||
|
@ -1,4 +1,12 @@
|
||||
|
||||
#page-type-view > section {
|
||||
transition: background-color .5s ease;
|
||||
|
||||
&.blurred {
|
||||
background-color: $grey-lighter;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.sd-menus {
|
||||
|
||||
|
@ -26,3 +26,14 @@ h2.nav-item {
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
.searchresults {
|
||||
position: fixed;
|
||||
top: 45px;
|
||||
left: 0;
|
||||
right: 0;
|
||||
margin: 0 auto;
|
||||
width: 500px;
|
||||
z-index: 1;
|
||||
//display: none;
|
||||
}
|
@ -1,38 +1,46 @@
|
||||
###################################################
|
||||
# REQUARKS WIKI - CONFIGURATION #
|
||||
###################################################
|
||||
# Full explanation + examples in the documentation (https://requarks-wiki.readme.io/)
|
||||
#######################################################################
|
||||
# REQUARKS WIKI - CONFIGURATION #
|
||||
#######################################################################
|
||||
# Full explanation + examples in the documentation:
|
||||
# https://requarks-wiki.readme.io/
|
||||
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Title of this site
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
title: Wiki
|
||||
|
||||
# -------------------------------------------------
|
||||
# Full path to the site, without the trailing slash
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Full public path to the site, without the trailing slash
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
host: http://localhost
|
||||
|
||||
# -------------------------------------------------
|
||||
# Port the server should listen to (80 by default)
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Port the main server should listen to (80 by default)
|
||||
# ---------------------------------------------------------------------
|
||||
# To use process.env.PORT, comment the line below:
|
||||
|
||||
port: 80
|
||||
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Port the websocket server should listen to (8080 by default)
|
||||
# ---------------------------------------------------------------------
|
||||
# Make sure this port is opened in the firewall if applicable
|
||||
|
||||
wsPort: 8080
|
||||
|
||||
# ---------------------------------------------------------------------
|
||||
# Data Directories
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
datadir:
|
||||
repo: ./repo
|
||||
db: ./data
|
||||
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Git Connection Info
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
|
||||
git:
|
||||
url: https://github.com/Organization/Repo
|
||||
@ -52,24 +60,25 @@ git:
|
||||
privateKey: /etc/requarkswiki/keys/git.key
|
||||
sslVerify: true
|
||||
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Secret key to use when encrypting sessions
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Use a long and unique random string (256-bit keys are perfect!)
|
||||
|
||||
sessionSecret: 1234567890abcdefghijklmnopqrstuvxyz
|
||||
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Administrator email
|
||||
# -------------------------------------------------
|
||||
# An account will be created using the email specified here.
|
||||
# The password is set to "admin123" by default. Change it immediately upon login!!!
|
||||
# ---------------------------------------------------------------------
|
||||
# An admin account will be created using the email specified here.
|
||||
# The password is set to "admin123" by default. Change it immediately
|
||||
# upon login!!!
|
||||
|
||||
admin: admin@company.com
|
||||
|
||||
# -------------------------------------------------
|
||||
# Site UI Language
|
||||
# -------------------------------------------------
|
||||
# ---------------------------------------------------------------------
|
||||
# Site Language
|
||||
# ---------------------------------------------------------------------
|
||||
# Possible values: en, fr
|
||||
|
||||
lang: en
|
@ -19,6 +19,7 @@ var include = require("gulp-include");
|
||||
*/
|
||||
var paths = {
|
||||
scriptlibs: [
|
||||
'./node_modules/socket.io-client/socket.io.js',
|
||||
'./node_modules/jquery/dist/jquery.min.js',
|
||||
'./node_modules/vue/dist/vue.min.js',
|
||||
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',
|
||||
|
32
lib/internalAuth.js
Normal file
32
lib/internalAuth.js
Normal file
@ -0,0 +1,32 @@
|
||||
"use strict";
|
||||
|
||||
const crypto = require('crypto');
|
||||
|
||||
/**
|
||||
* Internal Authentication
|
||||
*/
|
||||
module.exports = {
|
||||
|
||||
_curKey: false,
|
||||
|
||||
init(inKey) {
|
||||
|
||||
this._curKey = inKey;
|
||||
|
||||
return this;
|
||||
|
||||
},
|
||||
|
||||
generateKey() {
|
||||
|
||||
return crypto.randomBytes(20).toString('hex')
|
||||
|
||||
},
|
||||
|
||||
validateKey(inKey) {
|
||||
|
||||
return inKey === this._curKey;
|
||||
|
||||
}
|
||||
|
||||
};
|
@ -183,7 +183,7 @@ module.exports = {
|
||||
* @param {String} entryPath The entry path
|
||||
* @return {String} Text-only version
|
||||
*/
|
||||
fetchTextVersion(entryPath) {
|
||||
fetchIndexableVersion(entryPath) {
|
||||
|
||||
let self = this;
|
||||
|
||||
@ -192,11 +192,13 @@ module.exports = {
|
||||
parseMeta: true,
|
||||
parseTree: false,
|
||||
includeMarkdown: true,
|
||||
includeParentInfo: false,
|
||||
includeParentInfo: true,
|
||||
cache: false
|
||||
}).then((pageData) => {
|
||||
return {
|
||||
entryPath,
|
||||
meta: pageData.meta,
|
||||
parent: pageData.parent || {},
|
||||
text: mark.removeMarkdown(pageData.markdown)
|
||||
};
|
||||
});
|
||||
|
@ -160,7 +160,7 @@ module.exports = {
|
||||
|
||||
} else {
|
||||
|
||||
winston.info('[GIT] Repository is already in sync.');
|
||||
winston.info('[GIT] Push skipped. Repository is already in sync.');
|
||||
|
||||
}
|
||||
|
||||
|
@ -3,7 +3,7 @@
|
||||
var Promise = require('bluebird'),
|
||||
_ = require('lodash'),
|
||||
path = require('path'),
|
||||
searchIndex = Promise.promisifyAll(require('search-index')),
|
||||
searchIndex = require('search-index'),
|
||||
stopWord = require('stopword');
|
||||
|
||||
/**
|
||||
@ -21,9 +21,10 @@ module.exports = {
|
||||
*/
|
||||
init(appconfig) {
|
||||
|
||||
let self = this;
|
||||
let dbPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'search-index');
|
||||
|
||||
this._si = searchIndex({
|
||||
searchIndex({
|
||||
deletable: true,
|
||||
fieldedSearch: true,
|
||||
indexPath: dbPath,
|
||||
@ -32,11 +33,86 @@ module.exports = {
|
||||
}, (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, '')
|
||||
.split(' ')
|
||||
.filter((f) => { return !_.isEmpty(f); })
|
||||
.value();
|
||||
|
||||
return self._si.searchAsync({
|
||||
query: {
|
||||
AND: [{ '*': terms }]
|
||||
},
|
||||
pageSize: 10
|
||||
}).get('hits');
|
||||
|
||||
},
|
||||
|
||||
/**
|
||||
* 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._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: 'subtitle',
|
||||
searchable: false,
|
||||
},
|
||||
{
|
||||
fieldName: 'content',
|
||||
searchable: true,
|
||||
weight: 0,
|
||||
store: false
|
||||
}]
|
||||
}).then(() => {
|
||||
winston.info('Entry ' + content.entryPath + ' added to index.');
|
||||
}).catch((err) => {
|
||||
winston.error(err);
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
};
|
@ -76,6 +76,7 @@
|
||||
"search-index": "^0.8.15",
|
||||
"serve-favicon": "^2.3.0",
|
||||
"simplemde": "^1.11.2",
|
||||
"socket.io": "^1.4.8",
|
||||
"validator": "^5.5.0",
|
||||
"validator-as-promised": "^1.0.2",
|
||||
"winston": "^2.2.0"
|
||||
|
24
server.js
24
server.js
@ -10,7 +10,18 @@ global.ROOTPATH = __dirname;
|
||||
// Load global modules
|
||||
// ----------------------------------------
|
||||
|
||||
var _isDebug = process.env.NODE_ENV === 'development';
|
||||
|
||||
global.winston = require('winston');
|
||||
winston.remove(winston.transports.Console)
|
||||
winston.add(winston.transports.Console, {
|
||||
level: (_isDebug) ? 'info' : 'warn',
|
||||
prettyPrint: true,
|
||||
colorize: true,
|
||||
silent: false,
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
winston.info('[SERVER] Requarks Wiki is initializing...');
|
||||
|
||||
var appconfig = require('./models/config')('./config.yml');
|
||||
@ -161,8 +172,6 @@ app.use(function(err, req, res, next) {
|
||||
// Start HTTP server
|
||||
// ----------------------------------------
|
||||
|
||||
winston.info('[SERVER] Requarks Wiki has initialized successfully.');
|
||||
|
||||
winston.info('[SERVER] Starting HTTP server on port ' + appconfig.port + '...');
|
||||
|
||||
app.set('port', appconfig.port);
|
||||
@ -193,12 +202,17 @@ server.on('listening', () => {
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// Start Agents
|
||||
// Start child processes
|
||||
// ----------------------------------------
|
||||
|
||||
var fork = require('child_process').fork;
|
||||
var bgAgent = fork('agent.js');
|
||||
var fork = require('child_process').fork,
|
||||
libInternalAuth = require('./lib/internalAuth'),
|
||||
internalAuthKey = libInternalAuth.generateKey();
|
||||
|
||||
var wsSrv = fork('ws-server.js', [internalAuthKey]),
|
||||
bgAgent = fork('agent.js', [internalAuthKey]);
|
||||
|
||||
process.on('exit', (code) => {
|
||||
wsSrv.disconnect();
|
||||
bgAgent.disconnect();
|
||||
});
|
@ -1,34 +1,45 @@
|
||||
|
||||
nav.nav.has-shadow.stickyscroll#header
|
||||
.nav-left
|
||||
block rootNavLeft
|
||||
a.nav-item.is-brand(href='/')
|
||||
img(src='/favicons/android-icon-96x96.png', alt='Wiki')
|
||||
a.nav-item(href='/')
|
||||
h1.title Wiki
|
||||
.nav-center
|
||||
block rootNavCenter
|
||||
p.nav-item
|
||||
input.input(type='text', placeholder='Search...', style= { 'max-width': '300px', width: '33vw' })
|
||||
span.nav-toggle
|
||||
span
|
||||
span
|
||||
span
|
||||
.nav-right.nav-menu
|
||||
block rootNavRight
|
||||
i.nav-item#notifload
|
||||
a.nav-item(href='/history/' + pageData.meta.path)
|
||||
| History
|
||||
a.nav-item(href='/source/' + pageData.meta.path)
|
||||
| Source
|
||||
span.nav-item
|
||||
a.button(href='/edit/' + pageData.meta.path)
|
||||
span.icon
|
||||
i.fa.fa-edit
|
||||
span Edit
|
||||
a.button.is-primary.btn-create-prompt
|
||||
span.icon
|
||||
i.fa.fa-plus
|
||||
span Create
|
||||
#header-container
|
||||
nav.nav.has-shadow.stickyscroll#header
|
||||
.nav-left
|
||||
block rootNavLeft
|
||||
a.nav-item.is-brand(href='/')
|
||||
img(src='/favicons/android-icon-96x96.png', alt='Wiki')
|
||||
a.nav-item(href='/')
|
||||
h1.title Wiki
|
||||
.nav-center
|
||||
block rootNavCenter
|
||||
p.nav-item
|
||||
input.input(type='text', v-model='searchq', debounce='500' placeholder='Search...', style= { 'max-width': '300px', width: '33vw' })
|
||||
span.nav-toggle
|
||||
span
|
||||
span
|
||||
span
|
||||
.nav-right.nav-menu
|
||||
block rootNavRight
|
||||
i.nav-item#notifload
|
||||
a.nav-item(href='/history/' + pageData.meta.path)
|
||||
| History
|
||||
a.nav-item(href='/source/' + pageData.meta.path)
|
||||
| Source
|
||||
span.nav-item
|
||||
a.button(href='/edit/' + pageData.meta.path)
|
||||
span.icon
|
||||
i.fa.fa-edit
|
||||
span Edit
|
||||
a.button.is-primary.btn-create-prompt
|
||||
span.icon
|
||||
i.fa.fa-plus
|
||||
span Create
|
||||
|
||||
.box.searchresults
|
||||
.menu
|
||||
p.menu-label
|
||||
| Search Results
|
||||
ul.menu-list
|
||||
li(v-for='sres in searchres')
|
||||
a(href='#') {{ sres.document.title }}
|
||||
p.menu-label
|
||||
| Do you mean...?
|
||||
|
||||
|
||||
|
@ -24,6 +24,8 @@ html
|
||||
// JS
|
||||
script(type='text/javascript', src='/js/libs.js')
|
||||
script(type='text/javascript', src='/js/app.js')
|
||||
script(type='text/javascript').
|
||||
var ioHost = window.location.origin + ':!{appconfig.wsPort}/';
|
||||
|
||||
block head
|
||||
|
||||
|
137
ws-server.js
Normal file
137
ws-server.js
Normal file
@ -0,0 +1,137 @@
|
||||
// ===========================================
|
||||
// REQUARKS WIKI - WebSocket Server
|
||||
// 1.0.0
|
||||
// Licensed under AGPLv3
|
||||
// ===========================================
|
||||
|
||||
global.ROOTPATH = __dirname;
|
||||
|
||||
// ----------------------------------------
|
||||
// Load Winston
|
||||
// ----------------------------------------
|
||||
|
||||
var _isDebug = process.env.NODE_ENV === 'development';
|
||||
|
||||
global.winston = require('winston');
|
||||
winston.remove(winston.transports.Console)
|
||||
winston.add(winston.transports.Console, {
|
||||
level: (_isDebug) ? 'info' : 'warn',
|
||||
prettyPrint: true,
|
||||
colorize: true,
|
||||
silent: false,
|
||||
timestamp: true
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// Fetch internal handshake key
|
||||
// ----------------------------------------
|
||||
|
||||
if(!process.argv[2] || process.argv[2].length !== 40) {
|
||||
winston.error('[WS] Illegal process start. Missing handshake key.');
|
||||
process.exit(1);
|
||||
}
|
||||
global.internalAuth = require('./lib/internalAuth').init(process.argv[2]);;
|
||||
|
||||
// ----------------------------------------
|
||||
// Load modules
|
||||
// ----------------------------------------
|
||||
|
||||
winston.info('[WS] WS Server is initializing...');
|
||||
|
||||
var appconfig = require('./models/config')('./config.yml');
|
||||
|
||||
global.entries = require('./models/entries').init(appconfig);
|
||||
global.mark = require('./models/markdown');
|
||||
global.search = require('./models/search').init(appconfig);
|
||||
|
||||
// ----------------------------------------
|
||||
// Load modules
|
||||
// ----------------------------------------
|
||||
|
||||
var _ = require('lodash');
|
||||
var express = require('express');
|
||||
var path = require('path');
|
||||
var http = require('http');
|
||||
var socketio = require('socket.io');
|
||||
var moment = require('moment');
|
||||
|
||||
// ----------------------------------------
|
||||
// Define Express App
|
||||
// ----------------------------------------
|
||||
|
||||
global.app = express();
|
||||
|
||||
// ----------------------------------------
|
||||
// Controllers
|
||||
// ----------------------------------------
|
||||
|
||||
app.get('/', function(req, res){
|
||||
res.send('Requarks Wiki WebSocket server');
|
||||
});
|
||||
|
||||
// ----------------------------------------
|
||||
// Start WebSocket server
|
||||
// ----------------------------------------
|
||||
|
||||
winston.info('[SERVER] Starting WebSocket server on port ' + appconfig.wsPort + '...');
|
||||
|
||||
app.set('port', appconfig.wsPort);
|
||||
var server = http.Server(app);
|
||||
var io = socketio(server);
|
||||
|
||||
server.on('error', (error) => {
|
||||
if (error.syscall !== 'listen') {
|
||||
throw error;
|
||||
}
|
||||
|
||||
switch (error.code) {
|
||||
case 'EACCES':
|
||||
console.error('Listening on port ' + appconfig.port + ' requires elevated privileges!');
|
||||
process.exit(1);
|
||||
break;
|
||||
case 'EADDRINUSE':
|
||||
console.error('Port ' + appconfig.port + ' is already in use!');
|
||||
process.exit(1);
|
||||
break;
|
||||
default:
|
||||
throw error;
|
||||
}
|
||||
});
|
||||
|
||||
server.listen(appconfig.wsPort, () => {
|
||||
winston.info('[WS] WebSocket server started successfully! [RUNNING]');
|
||||
});
|
||||
|
||||
io.on('connection', (socket) => {
|
||||
|
||||
socket.on('searchAdd', (data) => {
|
||||
if(internalAuth.validateKey(data.auth)) {
|
||||
search.add(data.content);
|
||||
}
|
||||
});
|
||||
|
||||
socket.on('search', (data, cb) => {
|
||||
search.find(data.terms).then((results) => {
|
||||
cb(results);
|
||||
});
|
||||
})
|
||||
|
||||
});
|
||||
|
||||
/*setTimeout(() => {
|
||||
search._si.searchAsync({ query: { AND: [{'*': ['unit']}] }}).then((stuff) => { console.log(stuff.hits); });
|
||||
}, 8000);*/
|
||||
|
||||
// ----------------------------------------
|
||||
// Shutdown gracefully
|
||||
// ----------------------------------------
|
||||
|
||||
process.on('disconnect', () => {
|
||||
winston.warn('[WS] Lost connection to main server. Exiting... [' + moment().toISOString() + ']');
|
||||
server.close();
|
||||
process.exit();
|
||||
});
|
||||
|
||||
process.on('exit', () => {
|
||||
server.stop();
|
||||
});
|
Loading…
Reference in New Issue
Block a user