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;
// ----------------------------------------
// 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.

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
// ====================================

View File

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

View File

@ -25,4 +25,15 @@ h2.nav-item {
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 #
###################################################
# 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

View File

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

View File

@ -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.');
}

View File

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

View File

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

View File

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

View File

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

View File

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