WebSocket server + Search index + indexable content parser

This commit is contained in:
NGPixel 2016-09-05 00:39:59 -04:00
parent 528fab6c87
commit 7945d024ad
30 changed files with 488 additions and 107 deletions

View File

@ -6,11 +6,36 @@
global.ROOTPATH = __dirname; 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 // Load modules
// ---------------------------------------- // ----------------------------------------
global.winston = require('winston');
winston.info('[AGENT] Background Agent is initializing...'); winston.info('[AGENT] Background Agent is initializing...');
var appconfig = require('./models/config')('./config.yml'); var appconfig = require('./models/config')('./config.yml');
@ -18,7 +43,6 @@ var appconfig = require('./models/config')('./config.yml');
global.git = require('./models/git').init(appconfig); global.git = require('./models/git').init(appconfig);
global.entries = require('./models/entries').init(appconfig); global.entries = require('./models/entries').init(appconfig);
global.mark = require('./models/markdown'); global.mark = require('./models/markdown');
global.search = require('./models/search').init(appconfig);
var _ = require('lodash'); var _ = require('lodash');
var moment = require('moment'); var moment = require('moment');
@ -26,6 +50,8 @@ var Promise = require('bluebird');
var fs = Promise.promisifyAll(require("fs-extra")); var fs = Promise.promisifyAll(require("fs-extra"));
var path = require('path'); var path = require('path');
var cron = require('cron').CronJob; var cron = require('cron').CronJob;
var wsClient = require('socket.io-client');
global.ws = wsClient('http://localhost:' + appconfig.wsPort, { reconnectionAttempts: 10 });
// ---------------------------------------- // ----------------------------------------
// Start Cron // Start Cron
@ -90,8 +116,11 @@ var job = new cron({
//-> Update search index //-> Update search index
if(fileStatus !== 'active') { if(fileStatus !== 'active') {
return entries.fetchTextVersion(entryPath).then((content) => { return entries.fetchIndexableVersion(entryPath).then((content) => {
console.log(content); ws.emit('searchAdd', {
auth: WSInternalKey,
content
});
}); });
} }
@ -114,25 +143,42 @@ var job = new cron({
// ---------------------------------------- // ----------------------------------------
Promise.all(jobs).then(() => { 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) => { }).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(() => { }).finally(() => {
jobIsBusy = false; jobIsBusy = false;
}); });
}, },
start: true, start: false,
timeZone: 'UTC', timeZone: 'UTC',
runOnInit: true 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 // Shutdown gracefully
// ---------------------------------------- // ----------------------------------------
process.on('disconnect', () => { 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(); job.stop();
process.exit(); 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.

View File

@ -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

View File

@ -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 // Pages logic
// ==================================== // ====================================

View File

@ -1,4 +1,12 @@
#page-type-view > section {
transition: background-color .5s ease;
&.blurred {
background-color: $grey-lighter;
}
}
.sd-menus { .sd-menus {

View File

@ -25,4 +25,15 @@ h2.nav-item {
opacity: 1; opacity: 1;
} }
}
.searchresults {
position: fixed;
top: 45px;
left: 0;
right: 0;
margin: 0 auto;
width: 500px;
z-index: 1;
//display: none;
} }

View File

@ -1,38 +1,46 @@
################################################### #######################################################################
# REQUARKS WIKI - CONFIGURATION # # REQUARKS WIKI - CONFIGURATION #
################################################### #######################################################################
# Full explanation + examples in the documentation (https://requarks-wiki.readme.io/) # Full explanation + examples in the documentation:
# https://requarks-wiki.readme.io/
# ------------------------------------------------- # ---------------------------------------------------------------------
# Title of this site # Title of this site
# ------------------------------------------------- # ---------------------------------------------------------------------
title: Wiki title: Wiki
# ------------------------------------------------- # ---------------------------------------------------------------------
# Full path to the site, without the trailing slash # Full public path to the site, without the trailing slash
# ------------------------------------------------- # ---------------------------------------------------------------------
host: http://localhost 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: # To use process.env.PORT, comment the line below:
port: 80 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 # Data Directories
# ------------------------------------------------- # ---------------------------------------------------------------------
datadir: datadir:
repo: ./repo repo: ./repo
db: ./data db: ./data
# ------------------------------------------------- # ---------------------------------------------------------------------
# Git Connection Info # Git Connection Info
# ------------------------------------------------- # ---------------------------------------------------------------------
git: git:
url: https://github.com/Organization/Repo url: https://github.com/Organization/Repo
@ -52,24 +60,25 @@ git:
privateKey: /etc/requarkswiki/keys/git.key privateKey: /etc/requarkswiki/keys/git.key
sslVerify: true sslVerify: true
# ------------------------------------------------- # ---------------------------------------------------------------------
# Secret key to use when encrypting sessions # Secret key to use when encrypting sessions
# ------------------------------------------------- # ---------------------------------------------------------------------
# Use a long and unique random string (256-bit keys are perfect!) # Use a long and unique random string (256-bit keys are perfect!)
sessionSecret: 1234567890abcdefghijklmnopqrstuvxyz sessionSecret: 1234567890abcdefghijklmnopqrstuvxyz
# ------------------------------------------------- # ---------------------------------------------------------------------
# Administrator email # Administrator email
# ------------------------------------------------- # ---------------------------------------------------------------------
# An account will be created using the email specified here. # An admin account will be created using the email specified here.
# The password is set to "admin123" by default. Change it immediately upon login!!! # The password is set to "admin123" by default. Change it immediately
# upon login!!!
admin: admin@company.com admin: admin@company.com
# ------------------------------------------------- # ---------------------------------------------------------------------
# Site UI Language # Site Language
# ------------------------------------------------- # ---------------------------------------------------------------------
# Possible values: en, fr # Possible values: en, fr
lang: en lang: en

View File

@ -19,6 +19,7 @@ var include = require("gulp-include");
*/ */
var paths = { var paths = {
scriptlibs: [ scriptlibs: [
'./node_modules/socket.io-client/socket.io.js',
'./node_modules/jquery/dist/jquery.min.js', './node_modules/jquery/dist/jquery.min.js',
'./node_modules/vue/dist/vue.min.js', './node_modules/vue/dist/vue.min.js',
'./node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js', './node_modules/jquery-smooth-scroll/jquery.smooth-scroll.min.js',

32
lib/internalAuth.js Normal file
View 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;
}
};

View File

@ -183,7 +183,7 @@ module.exports = {
* @param {String} entryPath The entry path * @param {String} entryPath The entry path
* @return {String} Text-only version * @return {String} Text-only version
*/ */
fetchTextVersion(entryPath) { fetchIndexableVersion(entryPath) {
let self = this; let self = this;
@ -192,11 +192,13 @@ module.exports = {
parseMeta: true, parseMeta: true,
parseTree: false, parseTree: false,
includeMarkdown: true, includeMarkdown: true,
includeParentInfo: false, includeParentInfo: true,
cache: false cache: false
}).then((pageData) => { }).then((pageData) => {
return { return {
entryPath,
meta: pageData.meta, meta: pageData.meta,
parent: pageData.parent || {},
text: mark.removeMarkdown(pageData.markdown) text: mark.removeMarkdown(pageData.markdown)
}; };
}); });

View File

@ -160,7 +160,7 @@ module.exports = {
} else { } else {
winston.info('[GIT] Repository is already in sync.'); winston.info('[GIT] Push skipped. Repository is already in sync.');
} }

View File

@ -3,7 +3,7 @@
var Promise = require('bluebird'), var Promise = require('bluebird'),
_ = require('lodash'), _ = require('lodash'),
path = require('path'), path = require('path'),
searchIndex = Promise.promisifyAll(require('search-index')), searchIndex = require('search-index'),
stopWord = require('stopword'); stopWord = require('stopword');
/** /**
@ -21,9 +21,10 @@ module.exports = {
*/ */
init(appconfig) { init(appconfig) {
let self = this;
let dbPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'search-index'); let dbPath = path.resolve(ROOTPATH, appconfig.datadir.db, 'search-index');
this._si = searchIndex({ searchIndex({
deletable: true, deletable: true,
fieldedSearch: true, fieldedSearch: true,
indexPath: dbPath, indexPath: dbPath,
@ -32,11 +33,86 @@ module.exports = {
}, (err, si) => { }, (err, si) => {
if(err) { if(err) {
winston.error('Failed to initialize search-index.', 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);
});
} }
}; };

View File

@ -76,6 +76,7 @@
"search-index": "^0.8.15", "search-index": "^0.8.15",
"serve-favicon": "^2.3.0", "serve-favicon": "^2.3.0",
"simplemde": "^1.11.2", "simplemde": "^1.11.2",
"socket.io": "^1.4.8",
"validator": "^5.5.0", "validator": "^5.5.0",
"validator-as-promised": "^1.0.2", "validator-as-promised": "^1.0.2",
"winston": "^2.2.0" "winston": "^2.2.0"

View File

@ -10,7 +10,18 @@ global.ROOTPATH = __dirname;
// Load global modules // Load global modules
// ---------------------------------------- // ----------------------------------------
var _isDebug = process.env.NODE_ENV === 'development';
global.winston = require('winston'); 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...'); winston.info('[SERVER] Requarks Wiki is initializing...');
var appconfig = require('./models/config')('./config.yml'); var appconfig = require('./models/config')('./config.yml');
@ -161,8 +172,6 @@ app.use(function(err, req, res, next) {
// Start HTTP server // Start HTTP server
// ---------------------------------------- // ----------------------------------------
winston.info('[SERVER] Requarks Wiki has initialized successfully.');
winston.info('[SERVER] Starting HTTP server on port ' + appconfig.port + '...'); winston.info('[SERVER] Starting HTTP server on port ' + appconfig.port + '...');
app.set('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 fork = require('child_process').fork,
var bgAgent = fork('agent.js'); libInternalAuth = require('./lib/internalAuth'),
internalAuthKey = libInternalAuth.generateKey();
var wsSrv = fork('ws-server.js', [internalAuthKey]),
bgAgent = fork('agent.js', [internalAuthKey]);
process.on('exit', (code) => { process.on('exit', (code) => {
wsSrv.disconnect();
bgAgent.disconnect(); bgAgent.disconnect();
}); });

View File

@ -1,34 +1,45 @@
nav.nav.has-shadow.stickyscroll#header #header-container
.nav-left nav.nav.has-shadow.stickyscroll#header
block rootNavLeft .nav-left
a.nav-item.is-brand(href='/') block rootNavLeft
img(src='/favicons/android-icon-96x96.png', alt='Wiki') a.nav-item.is-brand(href='/')
a.nav-item(href='/') img(src='/favicons/android-icon-96x96.png', alt='Wiki')
h1.title Wiki a.nav-item(href='/')
.nav-center h1.title Wiki
block rootNavCenter .nav-center
p.nav-item block rootNavCenter
input.input(type='text', placeholder='Search...', style= { 'max-width': '300px', width: '33vw' }) p.nav-item
span.nav-toggle input.input(type='text', v-model='searchq', debounce='500' placeholder='Search...', style= { 'max-width': '300px', width: '33vw' })
span span.nav-toggle
span span
span span
.nav-right.nav-menu span
block rootNavRight .nav-right.nav-menu
i.nav-item#notifload block rootNavRight
a.nav-item(href='/history/' + pageData.meta.path) i.nav-item#notifload
| History a.nav-item(href='/history/' + pageData.meta.path)
a.nav-item(href='/source/' + pageData.meta.path) | History
| Source a.nav-item(href='/source/' + pageData.meta.path)
span.nav-item | Source
a.button(href='/edit/' + pageData.meta.path) span.nav-item
span.icon a.button(href='/edit/' + pageData.meta.path)
i.fa.fa-edit span.icon
span Edit i.fa.fa-edit
a.button.is-primary.btn-create-prompt span Edit
span.icon a.button.is-primary.btn-create-prompt
i.fa.fa-plus span.icon
span Create 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...?

View File

@ -24,6 +24,8 @@ html
// JS // JS
script(type='text/javascript', src='/js/libs.js') script(type='text/javascript', src='/js/libs.js')
script(type='text/javascript', src='/js/app.js') script(type='text/javascript', src='/js/app.js')
script(type='text/javascript').
var ioHost = window.location.origin + ':!{appconfig.wsPort}/';
block head block head

137
ws-server.js Normal file
View 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();
});