diff --git a/client/components/common/nav-header.vue b/client/components/common/nav-header.vue index 664bbeee..b5b801a6 100644 --- a/client/components/common/nav-header.vue +++ b/client/components/common/nav-header.vue @@ -120,27 +120,27 @@ v-list(nav, :light='!$vuetify.theme.dark', :dark='$vuetify.theme.dark', :class='$vuetify.theme.dark ? `grey darken-4` : ``') .overline.pa-4.grey--text {{$t('common:header.currentPage')}} v-list-item.pl-4(@click='pageView', v-if='mode !== `view`') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-box-outline + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-box-outline v-list-item-title.body-2 {{$t('common:header.view')}} v-list-item.pl-4(@click='pageEdit', v-if='mode !== `edit` && isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-file-document-edit-outline + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-file-document-edit-outline v-list-item-title.body-2 {{$t('common:header.edit')}} v-list-item.pl-4(@click='pageHistory', v-if='mode !== `history`') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-history + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-history v-list-item-content v-list-item-title.body-2 {{$t('common:header.history')}} v-list-item.pl-4(@click='pageSource', v-if='mode !== `source`') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-code-tags + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-code-tags v-list-item-title.body-2 {{$t('common:header.viewSource')}} v-list-item.pl-4(@click='pageDuplicate', v-if='isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-content-duplicate + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-content-duplicate v-list-item-title.body-2 {{$t('common:header.duplicate')}} v-list-item.pl-4(@click='pageMove', v-if='isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='indigo') mdi-content-save-move-outline + v-list-item-avatar(size='24', tile): v-icon(color='indigo') mdi-content-save-move-outline v-list-item-content v-list-item-title.body-2 {{$t('common:header.move')}} v-list-item.pl-4(@click='pageDelete', v-if='isAuthenticated') - v-list-item-avatar(size='24'): v-icon(color='red darken-2') mdi-trash-can-outline + v-list-item-avatar(size='24', tile): v-icon(color='red darken-2') mdi-trash-can-outline v-list-item-title.body-2 {{$t('common:header.delete')}} v-divider(vertical) diff --git a/config.sample.yml b/config.sample.yml index 5ceda6ad..73f208ff 100644 --- a/config.sample.yml +++ b/config.sample.yml @@ -130,7 +130,7 @@ offline: false # --------------------------------------------------------------------- # Set to true if you have multiple concurrent instances running off the # same DB (e.g. Kubernetes pods / load balanced instances). Leave false -# otherwise. +# otherwise. You MUST be using PostgreSQL to use this feature. ha: false diff --git a/server/core/auth.js b/server/core/auth.js index 850992c7..8f1d3cc0 100644 --- a/server/core/auth.js +++ b/server/core/auth.js @@ -281,6 +281,7 @@ module.exports = { async reloadGroups () { const groupsArray = await WIKI.models.groups.query() this.groups = _.keyBy(groupsArray, 'id') + WIKI.auth.guest = await WIKI.models.users.getGuestUser() }, /** @@ -324,6 +325,7 @@ module.exports = { ]) await WIKI.auth.activateStrategies() + WIKI.events.outbound.emit('reloadAuthStrategies') WIKI.logger.info('Regenerated certificates: [ COMPLETED ]') }, @@ -356,5 +358,20 @@ module.exports = { await guestUser.$relatedQuery('groups').relate(guestGroup.id) WIKI.logger.info('Guest user has been reset: [ COMPLETED ]') + }, + + /** + * Subscribe to HA propagation events + */ + subscribeToEvents() { + WIKI.events.inbound.on('reloadGroups', () => { + WIKI.auth.reloadGroups() + }) + WIKI.events.inbound.on('reloadApiKeys', () => { + WIKI.auth.reloadApiKeys() + }) + WIKI.events.inbound.on('reloadAuthStrategies', () => { + WIKI.auth.activateStrategies() + }) } } diff --git a/server/core/config.js b/server/core/config.js index 68187f2c..26c242e8 100644 --- a/server/core/config.js +++ b/server/core/config.js @@ -95,7 +95,7 @@ module.exports = { * @param {Array} keys Array of keys to save * @returns Promise */ - async saveToDb(keys) { + async saveToDb(keys, propagate = true) { try { for (let key of keys) { let value = _.get(WIKI.config, key, null) @@ -107,6 +107,9 @@ module.exports = { await WIKI.models.settings.query().insert({ key, value }) } } + if (propagate) { + WIKI.events.outbound.emit('reloadConfig') + } } catch (err) { WIKI.logger.error(`Failed to save configuration to DB: ${err.message}`) return false @@ -119,5 +122,15 @@ module.exports = { */ async applyFlags() { WIKI.models.knex.client.config.debug = WIKI.config.flags.sqllog + }, + + /** + * Subscribe to HA propagation events + */ + subscribeToEvents() { + WIKI.events.inbound.on('reloadConfig', async () => { + await WIKI.configSvc.loadFromDb() + await WIKI.configSvc.applyFlags() + }) } } diff --git a/server/core/db.js b/server/core/db.js index d16fc253..e2b4fbe4 100644 --- a/server/core/db.js +++ b/server/core/db.js @@ -203,12 +203,22 @@ module.exports = { WIKI.logger.debug(ev) } }) + + // -> Outbound events handling + this.listener.addChannel('wiki', payload => { if (_.has(payload.event) && payload.source !== WIKI.INSTANCE_ID) { - WIKI.events.emit(payload.event, payload.value) + WIKI.logger.debug(`Received event ${payload.event} from instance ${payload.source}: [ OK ]`) + WIKI.events.inbound.emit(payload.event, payload.value) } }) - WIKI.events.onAny(this.notifyViaDB) + WIKI.events.outbound.onAny(this.notifyViaDB) + + // -> Listen to inbound events + + WIKI.auth.subscribeToEvents() + WIKI.configSvc.subscribeToEvents() + WIKI.models.pages.subscribeToEvents() WIKI.logger.info(`High-Availability Listener initialized successfully: [ OK ]`) }, @@ -217,7 +227,8 @@ module.exports = { */ async unsubscribeToNotifications () { if (this.listener) { - WIKI.events.offAny(this.notifyViaDB) + WIKI.events.outbound.offAny(this.notifyViaDB) + WIKI.events.inbound.removeAllListeners() this.listener.close() } }, diff --git a/server/core/kernel.js b/server/core/kernel.js index fe8272c0..8961f08e 100644 --- a/server/core/kernel.js +++ b/server/core/kernel.js @@ -36,8 +36,10 @@ module.exports = { WIKI.scheduler = require('./scheduler').init() WIKI.servers = require('./servers') WIKI.sideloader = require('./sideloader').init() - WIKI.events = new EventEmitter() - await WIKI.models.subscribeToNotifications() + WIKI.events = { + inbound: new EventEmitter(), + outbound: new EventEmitter() + } } catch (err) { WIKI.logger.error(err) process.exit(1) @@ -77,6 +79,8 @@ module.exports = { await WIKI.models.searchEngines.initEngine() await WIKI.models.storage.initTargets() WIKI.scheduler.start() + + await WIKI.models.subscribeToNotifications() }, /** * Init Telemetry diff --git a/server/graph/resolvers/authentication.js b/server/graph/resolvers/authentication.js index a21793e7..68835a89 100644 --- a/server/graph/resolvers/authentication.js +++ b/server/graph/resolvers/authentication.js @@ -67,8 +67,11 @@ module.exports = { */ async createApiKey (obj, args, context) { try { + const key = await WIKI.models.apiKeys.createNewKey(args) + await WIKI.auth.reloadApiKeys() + WIKI.events.outbound.emit('reloadApiKeys') return { - key: await WIKI.models.apiKeys.createNewKey(args), + key, responseResult: graphHelper.generateSuccess('API Key created successfully') } } catch (err) { @@ -158,6 +161,7 @@ module.exports = { isRevoked: true }) await WIKI.auth.reloadApiKeys() + WIKI.events.outbound.emit('reloadApiKeys') return { responseResult: graphHelper.generateSuccess('API Key revoked successfully') } @@ -190,6 +194,7 @@ module.exports = { }).where('key', str.key) } await WIKI.auth.activateStrategies() + WIKI.events.outbound.emit('reloadAuthStrategies') return { responseResult: graphHelper.generateSuccess('Strategies updated successfully') } diff --git a/server/graph/resolvers/group.js b/server/graph/resolvers/group.js index 2e4ccea7..401ebb9f 100644 --- a/server/graph/resolvers/group.js +++ b/server/graph/resolvers/group.js @@ -54,6 +54,7 @@ module.exports = { isSystem: false }) await WIKI.auth.reloadGroups() + WIKI.events.outbound.emit('reloadGroups') return { responseResult: graphHelper.generateSuccess('Group created successfully.'), group @@ -62,6 +63,7 @@ module.exports = { async delete(obj, args) { await WIKI.models.groups.query().deleteById(args.id) await WIKI.auth.reloadGroups() + WIKI.events.outbound.emit('reloadGroups') return { responseResult: graphHelper.generateSuccess('Group has been deleted.') } @@ -94,6 +96,7 @@ module.exports = { }).where('id', args.id) await WIKI.auth.reloadGroups() + WIKI.events.outbound.emit('reloadGroups') return { responseResult: graphHelper.generateSuccess('Group has been updated.') diff --git a/server/graph/resolvers/page.js b/server/graph/resolvers/page.js index 13f75547..28b2b4a1 100644 --- a/server/graph/resolvers/page.js +++ b/server/graph/resolvers/page.js @@ -434,6 +434,7 @@ module.exports = { async flushCache(obj, args, context) { try { await WIKI.models.pages.flushCache() + WIKI.events.outbound.emit('flushCache') return { responseResult: graphHelper.generateSuccess('Pages Cache has been flushed successfully.') } diff --git a/server/graph/resolvers/system.js b/server/graph/resolvers/system.js index 0fd81893..af6befbc 100644 --- a/server/graph/resolvers/system.js +++ b/server/graph/resolvers/system.js @@ -205,6 +205,7 @@ module.exports = { if (args.groupMode !== `NONE`) { await WIKI.auth.reloadGroups() + WIKI.events.outbound.emit('reloadGroups') } client.close() diff --git a/server/index.js b/server/index.js index 24f2cbbf..939abf63 100644 --- a/server/index.js +++ b/server/index.js @@ -4,7 +4,7 @@ // =========================================== const path = require('path') -const nanoid = require('nanoid') +const { nanoid } = require('nanoid') let WIKI = { IS_DEBUG: process.env.NODE_ENV === 'development', diff --git a/server/models/pages.js b/server/models/pages.js index 6ec39f81..e1abbfa5 100644 --- a/server/models/pages.js +++ b/server/models/pages.js @@ -436,7 +436,8 @@ module.exports = class Page extends Model { localeCode: opts.destinationLocale, hash: destinationHash }).findById(page.id) - await WIKI.models.pages.deletePageFromCache(page) + await WIKI.models.pages.deletePageFromCache(page.hash) + WIKI.events.outbound.emit('deletePageFromCache', page.hash) // -> Rebuild page tree await WIKI.models.pages.rebuildTree() @@ -512,7 +513,8 @@ module.exports = class Page extends Model { // -> Delete page await WIKI.models.pages.query().delete().where('id', page.id) - await WIKI.models.pages.deletePageFromCache(page) + await WIKI.models.pages.deletePageFromCache(page.hash) + WIKI.events.outbound.emit('deletePageFromCache', page.hash) // -> Rebuild page tree await WIKI.models.pages.rebuildTree() @@ -609,7 +611,8 @@ module.exports = class Page extends Model { affectedHashes = qryHashes.map(h => h.hash) } for (const hash of affectedHashes) { - await WIKI.models.pages.deletePageFromCache({ hash }) + await WIKI.models.pages.deletePageFromCache(hash) + WIKI.events.outbound.emit('deletePageFromCache', hash) } } @@ -853,4 +856,16 @@ module.exports = class Page extends Model { .replace(/\s\s+/g, ' ') .split(' ').filter(w => w.length > 1).join(' ').toLowerCase() } + + /** + * Subscribe to HA propagation events + */ + static subscribeToEvents() { + WIKI.events.inbound.on('deletePageFromCache', hash => { + WIKI.models.pages.deletePageFromCache(hash) + }) + WIKI.events.inbound.on('flushCache', () => { + WIKI.models.pages.flushCache() + }) + } } diff --git a/server/setup.js b/server/setup.js index 38f6d097..d95225b0 100644 --- a/server/setup.js +++ b/server/setup.js @@ -186,7 +186,7 @@ module.exports = () => { 'telemetry', 'theming', 'title' - ]) + ], false) // Truncate tables (reset from previous failed install) await WIKI.models.locales.query().where('code', '!=', 'x').del() diff --git a/yarn.lock b/yarn.lock index 7c8c9a4c..3ef9e17e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2396,11 +2396,6 @@ dependencies: "@types/express" "*" -"@types/prettier@^1.19.0": - version "1.19.1" - resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" - integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== - "@types/pg-types@*": version "1.11.5" resolved "https://registry.yarnpkg.com/@types/pg-types/-/pg-types-1.11.5.tgz#1eebbe62b6772fcc75c18957a90f933d155e005b" @@ -2414,6 +2409,11 @@ "@types/node" "*" "@types/pg-types" "*" +"@types/prettier@^1.19.0": + version "1.19.1" + resolved "https://registry.yarnpkg.com/@types/prettier/-/prettier-1.19.1.tgz#33509849f8e679e4add158959fdb086440e9553f" + integrity sha512-5qOlnZscTn4xxM5MeGXAMOsIOIKIbh9e85zJWfBRVPlRMEVawzoPhINYbRGkBZCI8LxvBe7tJCdWiarA99OZfQ== + "@types/q@^1.5.1": version "1.5.2" resolved "https://registry.yarnpkg.com/@types/q/-/q-1.5.2.tgz#690a1475b84f2a884fd07cd797c00f5f31356ea8" @@ -11744,6 +11744,16 @@ pg-int8@1.0.1: resolved "https://registry.yarnpkg.com/pg-int8/-/pg-int8-1.0.1.tgz#943bd463bf5b71b4170115f80f8efc9a0c0eb78c" integrity sha512-WCtabS6t3c8SkpDBUlb1kjOs7l66xsGdKpIPZsg4wR+B3+u9UAum2odSsF9tnvxg80h4ZxLWMy4pRjOsFIqQpw== +pg-packet-stream@^1.1.0: + version "1.1.0" + resolved "https://registry.yarnpkg.com/pg-packet-stream/-/pg-packet-stream-1.1.0.tgz#e45c3ae678b901a2873af1e17b92d787962ef914" + integrity sha512-kRBH0tDIW/8lfnnOyTwKD23ygJ/kexQVXZs7gEyBljw4FYqimZFxnMMx50ndZ8In77QgfGuItS5LLclC2TtjYg== + +pg-pool@^2.0.10: + version "2.0.10" + resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-2.0.10.tgz#842ee23b04e86824ce9d786430f8365082d81c4a" + integrity sha512-qdwzY92bHf3nwzIUcj+zJ0Qo5lpG/YxchahxIN8+ZVmXqkahKXsnl2aiJPHLYN9o5mB/leG+Xh6XKxtP7e0sjg== + pg-pool@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/pg-pool/-/pg-pool-3.1.0.tgz#65f24bbda56cf7368f03ecdfd65e1da571041901" @@ -11754,10 +11764,6 @@ pg-protocol@^1.2.1: resolved "https://registry.yarnpkg.com/pg-protocol/-/pg-protocol-1.2.1.tgz#60adffeef131418c58f0b20df01ae8f507a95370" integrity sha512-IqZ+VUOqg3yydxSt5NgNKLVK9JgPBuzq4ZbA9GmrmIkQjQAszPT9DLqTtID0mKsLEZB68PU0gjLla561WZ2QkQ== -pg-query-stream@3.0.6: - version "3.0.6" - resolved "https://registry.yarnpkg.com/pg-query-stream/-/pg-query-stream-3.0.6.tgz#12f405c2c8c9723d8d9f1616cf3d58ecd1d87c83" - integrity sha512-/caOI36GVCz1pY35SkftzGowwym4p39e3Ku+sx8MZKNNf+G9WgE0h+Ui9FHTVV9HWf6WWu1GYt5aYfw5ZMeJsQ== pg-pubsub@0.5.0: version "0.5.0" resolved "https://registry.yarnpkg.com/pg-pubsub/-/pg-pubsub-0.5.0.tgz#5469737af32ac6d13fc3153dc3944f55da3d8840" @@ -11769,10 +11775,10 @@ pg-pubsub@0.5.0: promised-retry "^0.3.0" verror "^1.10.0" -pg-query-stream@3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/pg-query-stream/-/pg-query-stream-3.0.0.tgz#b2b4c3d5eb105df6cf75d3f95c4e157dd527a2ab" - integrity sha512-yxeFKwVCW0vmFYSkygV7hd4KVlGCMHGljSUQvYcIZUtfUaAITIl8QOq9oXfCMmk0L6JOuHesZKpurxyuOU8gKg== +pg-query-stream@3.0.6: + version "3.0.6" + resolved "https://registry.yarnpkg.com/pg-query-stream/-/pg-query-stream-3.0.6.tgz#12f405c2c8c9723d8d9f1616cf3d58ecd1d87c83" + integrity sha512-/caOI36GVCz1pY35SkftzGowwym4p39e3Ku+sx8MZKNNf+G9WgE0h+Ui9FHTVV9HWf6WWu1GYt5aYfw5ZMeJsQ== dependencies: pg-cursor "^2.1.9"