diff --git a/README.md b/README.md
index 725ecfe0..6c4d9cf2 100644
--- a/README.md
+++ b/README.md
@@ -14,13 +14,14 @@
[](http://standardjs.com/)
[](https://wiki.requarks.io/slack)
[](https://twitter.com/requarks)
+[](https://www.reddit.com/r/wikijs/)
[](https://blog.js.wiki/subscribe)
##### A modern, lightweight and powerful wiki app built on NodeJS
-- **[Official Website](https://wiki.js.org/)**
+- **[Official Website](https://js.wiki/)**
- **[Documentation](https://docs.requarks.io/)**
- [Requirements](https://docs.requarks.io/install/requirements)
- [Installation](https://docs.requarks.io/install)
@@ -74,6 +75,11 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
+
+
+ Jay Daley (@JayDaley)
+
+ |
Oleksii (@idokka)
@@ -84,18 +90,20 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
Theodore Chu (@TheodoreChu)
|
-
+
- Akira Suenami ([@a-suenami](https://github.com/a-suenami))
+- Arnaud Marchand ([@snuids](https://github.com/snuids))
- Bryon Vandiver ([@asterick](https://github.com/asterick))
+- Cameron Steele ([@ATechAdventurer](https://github.com/ATechAdventurer))
- Cloud Data Hosting LLC ([@CloudDataHostingLLC](https://github.com/CloudDataHostingLLC))
- CrazyMarvin ([@CrazyMarvin](https://github.com/CrazyMarvin))
- David Christian Holin ([@SirGibihm](https://github.com/SirGibihm))
@@ -104,9 +112,11 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- Ernie ([@iamernie](https://github.com/iamernie))
- Florian Moss ([@florianmoss](https://github.com/florianmoss))
- HeavenBay ([@HeavenBay](https://github.com/heavenbay))
+- Jaimyn Mayer ([@jabelone](https://github.com/jabelone))
- Jay Lee ([@polyglotm](https://github.com/polyglotm))
- Kelly Wardrop ([@dropcoded](https://github.com/dropcoded))
- Loki ([@binaryloki](https://github.com/binaryloki))
+- Marcilio Leite Neto ([@marclneto](https://github.com/marclneto))
- Mattias Johnson ([@mattiasJohnson](https://github.com/mattiasJohnson))
- Mitchell Rowton ([@mrowton](https://github.com/mrowton))
- M. Scott Ford ([@mscottford](https://github.com/mscottford))
@@ -122,6 +132,7 @@ Support this project by becoming a sponsor. Your name will show up in the Contri
- aniketpanjwani ([@aniketpanjwani](https://github.com/aniketpanjwani))
- aytaa ([@aytaa](https://github.com/aytaa))
- magicpotato ([@fortheday](https://github.com/fortheday))
+- motoacs ([@motoacs](https://github.com/motoacs))
- scorpion ([@scorpion](https://github.com/scorpion))
- valantien ([@valantien](https://github.com/valantien))
@@ -230,6 +241,7 @@ Thank you to all our patrons! 🙏 [[Become a patron](https://www.patreon.com/re
- Alex Zen
- Arti Zirk
- Brandon Curtis
+- Dave 'Sri' Seah
- djagoo
- Douglas Lassance
- Ernie Reid
@@ -238,6 +250,7 @@ Thank you to all our patrons! 🙏 [[Become a patron](https://www.patreon.com/re
- Florent
- Günter Pavlas
- hong
+- Hope
- Ian
- Iskander Callos
- Josh Stewart
@@ -245,9 +258,11 @@ Thank you to all our patrons! 🙏 [[Become a patron](https://www.patreon.com/re
- Keir
- Loïc CRAMPON
- Ludgeir Ibanez
+- Mark Mansur
- Matt Gedigian
- Patryk
- Philipp Schürch
+- Tracey Duffy
- Richeir
- SmartNET.works
- Stepan Sokolovskyi
diff --git a/client/components/admin/admin-pages.vue b/client/components/admin/admin-pages.vue
index 4bd5958c..0ca26de1 100644
--- a/client/components/admin/admin-pages.vue
+++ b/client/components/admin/admin-pages.vue
@@ -61,6 +61,7 @@
sort-by='updatedAt',
sort-desc,
hide-default-footer
+ @page-count="pageTotal = $event"
)
template(slot='item', slot-scope='props')
tr.is-clickable(:active='props.selected', @click='$router.push(`/pages/` + props.item.id)')
@@ -89,6 +90,7 @@ export default {
selectedPage: {},
pagination: 1,
pages: [],
+ pageTotal: 0,
headers: [
{ text: 'ID', value: 'id', width: 80, sortable: true },
{ text: 'Title', value: 'title' },
@@ -108,9 +110,6 @@ export default {
}
},
computed: {
- pageTotal () {
- return Math.ceil(this.filteredPages.length / 15)
- },
filteredPages () {
return _.filter(this.pages, pg => {
if (this.selectedLang !== null && this.selectedLang !== pg.locale) {
diff --git a/client/themes/default/components/page.vue b/client/themes/default/components/page.vue
index b02c40e8..42d0b2c5 100644
--- a/client/themes/default/components/page.vue
+++ b/client/themes/default/components/page.vue
@@ -564,11 +564,11 @@ export default {
if (window.location.hash && window.location.hash.length > 1) {
if (document.readyState === 'complete') {
this.$nextTick(() => {
- this.$vuetify.goTo(window.location.hash, this.scrollOpts)
+ this.$vuetify.goTo(decodeURIComponent(window.location.hash), this.scrollOpts)
})
} else {
window.addEventListener('load', () => {
- this.$vuetify.goTo(window.location.hash, this.scrollOpts)
+ this.$vuetify.goTo(decodeURIComponent(window.location.hash), this.scrollOpts)
})
}
}
@@ -579,7 +579,7 @@ export default {
el.onclick = ev => {
ev.preventDefault()
ev.stopPropagation()
- this.$vuetify.goTo(decodeURIComponent(ev.target.hash), this.scrollOpts)
+ this.$vuetify.goTo(decodeURIComponent(ev.currentTarget.hash), this.scrollOpts)
}
})
})
diff --git a/config.sample.yml b/config.sample.yml
index c149f702..316dd603 100644
--- a/config.sample.yml
+++ b/config.sample.yml
@@ -43,6 +43,9 @@ db:
# pfx: path/to/cert.pfx
# passphrase: xyz123
+ # Optional - PostgreSQL only:
+ schema: public
+
# SQLite only:
storage: path/to/database.sqlite
diff --git a/dev/helm/Chart.yaml b/dev/helm/Chart.yaml
index e284c368..614473a0 100644
--- a/dev/helm/Chart.yaml
+++ b/dev/helm/Chart.yaml
@@ -2,7 +2,7 @@ apiVersion: v2
name: wiki
# This is the chart version. This version number should be incremented each time you make changes
# to the chart and its templates, including the app version.
-version: 2.1.0
+version: 2.2.0
# This is the version number of the application being deployed. This version number should be
# incremented each time you make changes to the application.
AppVersion: latest
diff --git a/dev/helm/README.md b/dev/helm/README.md
index 7d45fba1..9e6192db 100644
--- a/dev/helm/README.md
+++ b/dev/helm/README.md
@@ -107,6 +107,8 @@ The following table lists the configurable parameters of the Wiki.js chart and t
| `postgresql.postgresqlUser` | Postgres username | `postgres` |
| `postgresql.postgresqlHost` | External postgres host | `nil` |
| `postgresql.postgresqlPassword` | External postgres password | `nil` |
+| `postgresql.existingSecret` | Provide an existing `Secret` for postgres | `nil` |
+| `postgresql.existingSecretKey` | The postgres password key in the existing `Secret` | `postgresql-password` |
| `postgresql.postgresqlPort` | External postgres port | `5432` |
| `postgresql.ssl` | Enable external postgres SSL connection | `false` |
| `postgresql.ca` | Certificate of Authority path for postgres | `nil` |
@@ -137,11 +139,11 @@ By default, PostgreSQL is installed as part of the chart.
### Using an external PostgreSQL server
-To use an external PostgreSQL server, set `postgresql.enabled` to `false` and then set `postgresql.postgresqlHost` and `postgresql.postgresqlPassword`. The other options (`postgresql.postgresqlDatabase`, `postgresql.postgresqlUser` and `postgresql.postgresqlPort`) may also want changing from their default values.
+To use an external PostgreSQL server, set `postgresql.enabled` to `false` and then set `postgresql.postgresqlHost` and `postgresql.postgresqlPassword`. To use an existing `Secret`, set `postgresql.existingSecret`. The other options (`postgresql.postgresqlDatabase`, `postgresql.postgresqlUser`, `postgresql.postgresqlPort` and `postgresql.existingSecretKey`) may also want changing from their default values.
To use an SSL connection you can set `postgresql.ssl` to `true` and if needed the path to a Certificate of Authority can be set using `postgresql.ca` to `/path/to/ca`. Default `postgresql.ssl` value is `false`.
-You also need to add the follow Helm template to your deployment:
+If `postgresql.existingSecret` is not specified, you also need to add the following Helm template to your deployment in order to create the postgresql `Secret`:
```yaml
kind: Secret
@@ -159,4 +161,4 @@ See the [Configuration](#configuration) section to configure the PVC or to disab
## Ingress
-This chart provides support for Ingress resource. If you have an available Ingress Controller such as Nginx or Traefik you maybe want to set `ingress.enabled` to true and choose an `ingress.hostname` for the URL. Then, you should be able to access the installation using that address.
+This chart provides support for Ingress resource. If you have an available Ingress Controller such as Nginx or Traefik you maybe want to set `ingress.enabled` to true and add `ingress.hosts` for the URL. Then, you should be able to access the installation using that address.
diff --git a/dev/helm/templates/deployment.yaml b/dev/helm/templates/deployment.yaml
index 0c728770..6c6a2ec9 100644
--- a/dev/helm/templates/deployment.yaml
+++ b/dev/helm/templates/deployment.yaml
@@ -39,9 +39,9 @@ spec:
- name: DB_USER
value: {{ default "wiki" .Values.postgresql.postgresqlUser }}
- name: DB_SSL
- value: "{{ default "false" .Values.postgresql.ssl }}"
+ value: "{{ default "false" .Values.postgresql.ssl }}"
- name: DB_SSL_CA
- value: "{{ default "" .Values.postgresql.ca }}"
+ value: "{{ default "" .Values.postgresql.ca }}"
- name: DB_PASS
valueFrom:
secretKeyRef:
@@ -51,18 +51,16 @@ spec:
name: {{ template "wiki.postgresql.secret" . }}
{{- end }}
key: {{ template "wiki.postgresql.secretKey" . }}
+ - name: HA
+ value: {{ .Values.replicaCount | int | le 2 | quote }}
ports:
- name: http
containerPort: 3000
protocol: TCP
livenessProbe:
- httpGet:
- path: /healthz
- port: http
+ {{- toYaml .Values.livenessProbe | nindent 12 }}
readinessProbe:
- httpGet:
- path: /healthz
- port: http
+ {{- toYaml .Values.readinessProbe | nindent 12 }}
resources:
{{- toYaml .Values.resources | nindent 12 }}
{{- with .Values.nodeSelector }}
diff --git a/dev/helm/templates/ingress.yaml b/dev/helm/templates/ingress.yaml
index 0b08134d..adf10b65 100644
--- a/dev/helm/templates/ingress.yaml
+++ b/dev/helm/templates/ingress.yaml
@@ -1,11 +1,18 @@
{{- if .Values.ingress.enabled -}}
-{{- $fullName := include "wiki.fullname" . -}}
-{{- $svcPort := .Values.service.port -}}
-{{- if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
+ {{- $fullName := include "wiki.fullname" . -}}
+ {{- $svcPort := .Values.service.port -}}
+ {{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }}
+ {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }}
+ {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}}
+ {{- end }}
+ {{- end }}
+ {{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}}
+apiVersion: networking.k8s.io/v1
+ {{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}}
apiVersion: networking.k8s.io/v1beta1
-{{- else -}}
+ {{- else -}}
apiVersion: extensions/v1beta1
-{{- end }}
+ {{- end }}
kind: Ingress
metadata:
name: {{ $fullName }}
@@ -31,11 +38,21 @@ spec:
- host: {{ .host | quote }}
http:
paths:
- {{- range .paths }}
- - path: {{ . }}
+ {{- range .paths }}
+ - path: {{ .path }}
+ {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }}
+ pathType: {{ .pathType }}
+ {{- end }}
backend:
+ {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }}
+ service:
+ name: {{ $fullName }}
+ port:
+ number: {{ $svcPort }}
+ {{- else }}
serviceName: {{ $fullName }}
servicePort: {{ $svcPort }}
- {{- end }}
+ {{- end }}
+ {{- end }}
+ {{- end }}
{{- end }}
-{{- end }}
diff --git a/dev/helm/values.yaml b/dev/helm/values.yaml
index 87701fb2..bfa9fb3c 100644
--- a/dev/helm/values.yaml
+++ b/dev/helm/values.yaml
@@ -21,6 +21,16 @@ serviceAccount:
# If not set and create is true, a name is generated using the fullname template
name:
+livenessProbe:
+ httpGet:
+ path: /healthz
+ port: http
+
+readinessProbe:
+ httpGet:
+ path: /healthz
+ port: http
+
podSecurityContext: {}
# fsGroup: 2000
@@ -42,13 +52,16 @@ service:
# annotations: {}
ingress:
- enabled: false
+ enabled: true
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- - host: wiki.local
- paths: ["/"]
+ - host: wiki.minikube.local
+ paths:
+ - path: "/"
+ pathType: Prefix
+
tls: []
# - secretName: chart-example-tls
# hosts:
@@ -82,7 +95,7 @@ postgresql:
enabled: true
## ssl enforce SSL communication with PostgresSQL
## Default to false
- ##
+ ##
# ssl: false
## ca Certificate of Authority
## Default to empty, point to location of CA
@@ -92,7 +105,7 @@ postgresql:
## Default to postgres
##
# postgresqlHost: postgres
- ## postgresqlPort port for postgres
+ ## postgresqlPort port for postgres
## Default to 5432
##
# postgresqlPort: 5432
diff --git a/package.json b/package.json
index 682b4138..587381db 100644
--- a/package.json
+++ b/package.json
@@ -36,7 +36,6 @@
"node": ">=10.12"
},
"dependencies": {
- "@aoberoi/passport-slack": "1.0.5",
"@azure/storage-blob": "12.2.1",
"@exlinc/keycloak-passport": "1.0.2",
"@joplin/turndown-plugin-gfm": "1.0.27",
@@ -151,6 +150,7 @@
"passport-okta-oauth": "0.0.1",
"passport-openidconnect": "0.0.2",
"passport-saml": "1.3.5",
+ "passport-slack-oauth2": "1.1.1",
"passport-twitch-oauth": "1.0.0",
"pem-jwk": "2.0.0",
"pg": "8.4.1",
diff --git a/server/controllers/upload.js b/server/controllers/upload.js
index c6a3685d..3da4dcac 100644
--- a/server/controllers/upload.js
+++ b/server/controllers/upload.js
@@ -76,7 +76,7 @@ router.post('/u', (req, res, next) => {
}
// Sanitize filename
- fileMeta.originalname = sanitize(fileMeta.originalname.toLowerCase().replace(/[\s,;]+/g, '_'))
+ fileMeta.originalname = sanitize(fileMeta.originalname.toLowerCase().replace(/[\s,;#]+/g, '_'))
// Check if user can upload at path
const assetPath = (folderId) ? hierarchy.map(h => h.slug).join('/') + `/${fileMeta.originalname}` : fileMeta.originalname
diff --git a/server/core/asar.js b/server/core/asar.js
index 6c76468d..25099016 100644
--- a/server/core/asar.js
+++ b/server/core/asar.js
@@ -40,11 +40,11 @@ module.exports = {
}
},
async unload () {
- if (this.fdCache) {
+ const fds = Object.values(this.fdCache)
+ if (fds.length > 0) {
WIKI.logger.info('Closing ASAR file descriptors...')
- for (const fdItem in this.fdCache) {
- fs.closeSync(this.fdCache[fdItem].fd)
- }
+ const closeAsync = require('util').promisify(fs.close)
+ await Promise.all(fds.map(x => closeAsync(x.fd)))
this.fdCache = {}
}
},
diff --git a/server/core/db.js b/server/core/db.js
index f1ac88b3..2d614c55 100644
--- a/server/core/db.js
+++ b/server/core/db.js
@@ -138,6 +138,10 @@ module.exports = {
switch (WIKI.config.db.type) {
case 'postgres':
await conn.query(`set application_name = 'Wiki.js'`)
+ // -> Set schema if it's not public
+ if (WIKI.config.db.schema && WIKI.config.db.schema !== 'public') {
+ await conn.query(`set search_path TO ${WIKI.config.db.schema}, public;`)
+ }
done()
break
case 'mysql':
diff --git a/server/core/kernel.js b/server/core/kernel.js
index af22c902..6dcb17cf 100644
--- a/server/core/kernel.js
+++ b/server/core/kernel.js
@@ -107,19 +107,21 @@ module.exports = {
* Graceful shutdown
*/
async shutdown () {
- if (WIKI.models) {
- await WIKI.models.unsubscribeToNotifications()
- await WIKI.models.knex.client.pool.destroy()
- await WIKI.models.knex.destroy()
+ if (WIKI.servers) {
+ await WIKI.servers.stopServers()
}
if (WIKI.scheduler) {
- WIKI.scheduler.stop()
+ await WIKI.scheduler.stop()
+ }
+ if (WIKI.models) {
+ await WIKI.models.unsubscribeToNotifications()
+ if (WIKI.models.knex) {
+ await WIKI.models.knex.destroy()
+ }
}
if (WIKI.asar) {
await WIKI.asar.unload()
}
- if (WIKI.servers) {
- await WIKI.servers.stopServers()
- }
+ process.exit(0)
}
}
diff --git a/server/core/scheduler.js b/server/core/scheduler.js
index 34c4e1f6..9eec70a7 100644
--- a/server/core/scheduler.js
+++ b/server/core/scheduler.js
@@ -12,7 +12,8 @@ class Job {
schedule = 'P1D',
repeat = false,
worker = false
- }) {
+ }, queue) {
+ this.queue = queue
this.finished = Promise.resolve()
this.name = name
this.immediate = immediate
@@ -27,10 +28,11 @@ class Job {
* @param {Object} data Job Data
*/
start(data) {
+ this.queue.jobs.push(this)
if (this.immediate) {
this.invoke(data)
} else {
- this.queue(data)
+ this.enqueue(data)
}
}
@@ -39,7 +41,7 @@ class Job {
*
* @param {Object} data Job Data
*/
- queue(data) {
+ enqueue(data) {
this.timeout = setTimeout(this.invoke.bind(this), this.schedule.asMilliseconds(), data)
}
@@ -55,14 +57,22 @@ class Job {
`--job=${this.name}`,
`--data=${data}`
], {
- cwd: WIKI.ROOTPATH
+ cwd: WIKI.ROOTPATH,
+ stdio: ['inherit', 'inherit', 'pipe', 'ipc']
})
+ const stderr = [];
+ proc.stderr.on('data', chunk => stderr.push(chunk))
this.finished = new Promise((resolve, reject) => {
proc.on('exit', (code, signal) => {
+ const data = Buffer.concat(stderr).toString()
if (code === 0) {
- resolve()
+ resolve(data)
} else {
- reject(signal)
+ const err = new Error(`Error when running job ${this.name}: ${data}`)
+ err.exitSignal = signal
+ err.exitCode = code
+ err.stderr = data
+ reject(err)
}
proc.kill()
})
@@ -74,16 +84,20 @@ class Job {
} catch (err) {
WIKI.logger.warn(err)
}
- if (this.repeat) {
- this.queue(data)
+ if (this.repeat && this.queue.jobs.includes(this)) {
+ this.enqueue(data)
+ } else {
+ this.stop().catch(() => {})
}
}
/**
* Stop any future job invocation from occuring
*/
- stop() {
+ async stop() {
clearTimeout(this.timeout)
+ this.queue.jobs = this.queue.jobs.filter(x => x !== this)
+ return this.finished
}
}
@@ -110,16 +124,11 @@ module.exports = {
})
},
registerJob(opts, data) {
- const job = new Job(opts)
+ const job = new Job(opts, this)
job.start(data)
- if (job.repeat) {
- this.jobs.push(job)
- }
return job
},
- stop() {
- this.jobs.forEach(job => {
- job.stop()
- })
+ async stop() {
+ return Promise.all(this.jobs.map(job => job.stop()))
}
}
diff --git a/server/core/worker.js b/server/core/worker.js
index bb983c07..d648a678 100644
--- a/server/core/worker.js
+++ b/server/core/worker.js
@@ -14,6 +14,11 @@ WIKI.logger = require('./logger').init('JOB')
const args = require('yargs').argv
;(async () => {
- await require(`../jobs/${args.job}`)(args.data)
- process.exit(0)
+ try {
+ await require(`../jobs/${args.job}`)(args.data)
+ process.exit(0)
+ } catch (e) {
+ await new Promise(resolve => process.stderr.write(e.message, resolve))
+ process.exit(1)
+ }
})()
diff --git a/server/index.js b/server/index.js
index 254335c9..87eff16b 100644
--- a/server/index.js
+++ b/server/index.js
@@ -33,3 +33,16 @@ WIKI.logger = require('./core/logger').init('MASTER')
// ----------------------------------------
WIKI.kernel.init()
+
+// ----------------------------------------
+// Register exit handler
+// ----------------------------------------
+
+process.on('SIGINT', () => {
+ WIKI.kernel.shutdown()
+})
+process.on('message', (msg) => {
+ if (msg === 'shutdown') {
+ WIKI.kernel.shutdown()
+ }
+})
diff --git a/server/jobs/rebuild-tree.js b/server/jobs/rebuild-tree.js
index 53d3898a..c2fc3728 100644
--- a/server/jobs/rebuild-tree.js
+++ b/server/jobs/rebuild-tree.js
@@ -74,5 +74,7 @@ module.exports = async (pageId) => {
} catch (err) {
WIKI.logger.error(`Rebuilding page tree: [ FAILED ]`)
WIKI.logger.error(err.message)
+ // exit process with error code
+ throw err
}
}
diff --git a/server/jobs/render-page.js b/server/jobs/render-page.js
index 8f4bc45a..3a88b375 100644
--- a/server/jobs/render-page.js
+++ b/server/jobs/render-page.js
@@ -90,5 +90,7 @@ module.exports = async (pageId) => {
} catch (err) {
WIKI.logger.error(`Rendering page ID ${pageId}: [ FAILED ]`)
WIKI.logger.error(err.message)
+ // exit process with error code
+ throw err
}
}
diff --git a/server/middlewares/upload.js b/server/middlewares/upload.js
deleted file mode 100644
index 15cd0d02..00000000
--- a/server/middlewares/upload.js
+++ /dev/null
@@ -1,6 +0,0 @@
-const { graphqlUploadExpress } = require('graphql-upload')
-
-/**
- * GraphQL File Upload Middleware
- */
-module.exports = graphqlUploadExpress({ maxFileSize: 5000000, maxFiles: 20 })
diff --git a/server/models/pages.js b/server/models/pages.js
index abe2bc6e..cd786374 100644
--- a/server/models/pages.js
+++ b/server/models/pages.js
@@ -650,7 +650,15 @@ module.exports = class Page extends Model {
* @returns {Promise} Promise with no value
*/
static async movePage(opts) {
- const page = await WIKI.models.pages.query().findById(opts.id)
+ let page
+ if (_.has(opts, 'id')) {
+ page = await WIKI.models.pages.query().findById(opts.id)
+ } else {
+ page = await WIKI.models.pages.query().findOne({
+ path: opts.path,
+ localeCode: opts.locale
+ })
+ }
if (!page) {
throw new WIKI.Error.PageNotFound()
}
@@ -704,9 +712,11 @@ module.exports = class Page extends Model {
const destinationHash = pageHelper.generateHash({ path: opts.destinationPath, locale: opts.destinationLocale, privateNS: opts.isPrivate ? 'TODO' : '' })
// -> Move page
+ const destinationTitle = (page.title === page.path ? opts.destinationPath : page.title)
await WIKI.models.pages.query().patch({
path: opts.destinationPath,
localeCode: opts.destinationLocale,
+ title: destinationTitle,
hash: destinationHash
}).findById(page.id)
await WIKI.models.pages.deletePageFromCache(page.hash)
@@ -775,7 +785,7 @@ module.exports = class Page extends Model {
})
}
if (!page) {
- throw new Error('Invalid Page Id')
+ throw new WIKI.Error.PageNotFound()
}
// -> Check for page access
diff --git a/server/modules/analytics/newrelic/code.yml b/server/modules/analytics/newrelic/code.yml
index 366e4b19..06c5bb7e 100644
--- a/server/modules/analytics/newrelic/code.yml
+++ b/server/modules/analytics/newrelic/code.yml
@@ -1,5 +1,5 @@
head: |
diff --git a/server/modules/analytics/newrelic/definition.yml b/server/modules/analytics/newrelic/definition.yml
index e8dd2d45..01e92235 100644
--- a/server/modules/analytics/newrelic/definition.yml
+++ b/server/modules/analytics/newrelic/definition.yml
@@ -16,3 +16,15 @@ props:
title: Application ID
hint: Found at the very end of the code snippet provided by New Relic Browser
order: 2
+ beacon:
+ type: String
+ title: Beacon
+ default: bam.nr-data.net
+ hint: Found at the very end of the code snippet provided by New Relic Browser. Differs for US and EU servers.
+ order: 3
+ errorBeacon:
+ type: String
+ title: Error Beacon
+ default: bam.nr-data.net
+ hint: Found at the very end of the code snippet provided by New Relic Browser. Differs for US and EU servers.
+ order: 4
diff --git a/server/modules/authentication/google/authentication.js b/server/modules/authentication/google/authentication.js
index 23eb40af..d7ba3b32 100644
--- a/server/modules/authentication/google/authentication.js
+++ b/server/modules/authentication/google/authentication.js
@@ -9,27 +9,38 @@ const _ = require('lodash')
module.exports = {
init (passport, conf) {
- passport.use('google',
- new GoogleStrategy({
- clientID: conf.clientId,
- clientSecret: conf.clientSecret,
- callbackURL: conf.callbackURL,
- passReqToCallback: true
- }, async (req, accessToken, refreshToken, profile, cb) => {
- try {
- const user = await WIKI.models.users.processProfile({
- providerKey: req.params.strategy,
- profile: {
- ...profile,
- picture: _.get(profile, 'photos[0].value', '')
- }
- })
- cb(null, user)
- } catch (err) {
- cb(err, null)
+ const strategy = new GoogleStrategy({
+ clientID: conf.clientId,
+ clientSecret: conf.clientSecret,
+ callbackURL: conf.callbackURL,
+ passReqToCallback: true
+ }, async (req, accessToken, refreshToken, profile, cb) => {
+ try {
+ if (conf.hostedDomain && conf.hostedDomain != profile._json.hd) {
+ throw new Error('Google authentication should have been performed with domain ' + conf.hostedDomain)
}
- })
- )
+ const user = await WIKI.models.users.processProfile({
+ providerKey: req.params.strategy,
+ profile: {
+ ...profile,
+ picture: _.get(profile, 'photos[0].value', '')
+ }
+ })
+ cb(null, user)
+ } catch (err) {
+ cb(err, null)
+ }
+ })
+
+ if (conf.hostedDomain) {
+ strategy.authorizationParams = function(options) {
+ return {
+ hd: conf.hostedDomain
+ }
+ }
+ }
+
+ passport.use('google', strategy)
},
logout (conf) {
return '/'
diff --git a/server/modules/authentication/google/definition.yml b/server/modules/authentication/google/definition.yml
index 70f2892d..51747c37 100644
--- a/server/modules/authentication/google/definition.yml
+++ b/server/modules/authentication/google/definition.yml
@@ -22,3 +22,8 @@ props:
title: Client Secret
hint: Application Client Secret
order: 2
+ hostedDomain:
+ type: String
+ title: Hosted Domain
+ hint: (optional) Only for G Suite hosted domain. Leave empty otherwise.
+ order: 3
diff --git a/server/modules/authentication/slack/authentication.js b/server/modules/authentication/slack/authentication.js
index 3e4b48c1..984ce28a 100644
--- a/server/modules/authentication/slack/authentication.js
+++ b/server/modules/authentication/slack/authentication.js
@@ -4,7 +4,7 @@
// Slack Account
// ------------------------------------
-const SlackStrategy = require('@aoberoi/passport-slack').default.Strategy
+const SlackStrategy = require('passport-slack-oauth2').Strategy
const _ = require('lodash')
module.exports = {
@@ -15,8 +15,9 @@ module.exports = {
clientSecret: conf.clientSecret,
callbackURL: conf.callbackURL,
team: conf.team,
+ scope: ['identity.basic', 'identity.email', 'identity.avatar'],
passReqToCallback: true
- }, async (req, accessToken, scopes, team, extra, { user: userProfile }, cb) => {
+ }, async (req, accessToken, refreshToken, { user: userProfile }, cb) => {
try {
const user = await WIKI.models.users.processProfile({
providerKey: req.params.strategy,
diff --git a/server/modules/rendering/html-core/renderer.js b/server/modules/rendering/html-core/renderer.js
index faa3549a..6ec35a66 100644
--- a/server/modules/rendering/html-core/renderer.js
+++ b/server/modules/rendering/html-core/renderer.js
@@ -237,7 +237,7 @@ module.exports = {
// --------------------------------
$('body').contents().toArray().forEach(item => {
- if (item.type === 'text' && item.parent.name === 'body') {
+ if (item && item.type === 'text' && item.parent.name === 'body') {
$(item).wrap('')
}
})
@@ -249,7 +249,7 @@ module.exports = {
function iterateMustacheNode (node) {
const list = $(node).contents().toArray()
list.forEach(item => {
- if (item.type === 'text') {
+ if (item && item.type === 'text') {
const rawText = $(item).text().replace(/\r?\n|\r/g, '')
if (mustacheRegExp.test(rawText)) {
$(item).parent().attr('v-pre', true)
diff --git a/server/modules/storage/git/storage.js b/server/modules/storage/git/storage.js
index c023c7e8..aff0c0d3 100644
--- a/server/modules/storage/git/storage.js
+++ b/server/modules/storage/git/storage.js
@@ -142,7 +142,9 @@ module.exports = {
if (_.get(diff, 'files', []).length > 0) {
let filesToProcess = []
for (const f of diff.files) {
- const fPath = path.join(this.repoPath, f.file)
+ const fMoved = f.file.split(' => ')
+ const fName = fMoved.length === 2 ? fMoved[1] : fMoved[0]
+ const fPath = path.join(this.repoPath, fName)
let fStats = { size: 0 }
try {
fStats = await fs.stat(fPath)
@@ -159,7 +161,8 @@ module.exports = {
path: fPath,
stats: fStats
},
- relPath: f.file
+ oldPath: fMoved[0],
+ relPath: fName
})
}
await this.processFiles(filesToProcess, rootUser)
@@ -174,11 +177,25 @@ module.exports = {
async processFiles(files, user) {
for (const item of files) {
const contentType = pageHelper.getContentType(item.relPath)
- const fileExists = await fs.pathExists(item.file)
+ const fileExists = await fs.pathExists(item.file.path)
if (!item.binary && contentType) {
// -> Page
- if (!fileExists && item.deletions > 0 && item.insertions === 0) {
+ if (fileExists && item.relPath !== item.oldPath) {
+ // Page was renamed by git, so rename in DB
+ WIKI.logger.info(`(STORAGE/GIT) Page marked as renamed: from ${item.oldPath} to ${item.relPath}`)
+
+ const contentPath = pageHelper.getPagePath(item.oldPath)
+ const contentDestinationPath = pageHelper.getPagePath(item.relPath)
+ await WIKI.models.pages.movePage({
+ user: user,
+ path: contentPath.path,
+ destinationPath: contentDestinationPath.path,
+ locale: contentPath.locale,
+ destinationLocale: contentPath.locale,
+ skipStorage: true
+ })
+ } else if (!fileExists && item.deletions > 0 && item.insertions === 0) {
// Page was deleted by git, can safely mark as deleted in DB
WIKI.logger.info(`(STORAGE/GIT) Page marked as deleted: ${item.relPath}`)
@@ -207,7 +224,23 @@ module.exports = {
} else {
// -> Asset
- if (!fileExists && ((item.before > 0 && item.after === 0) || (item.deletions > 0 && item.insertions === 0))) {
+ if (fileExists && ((item.before === item.after) || (item.deletions === 0 && item.insertions === 0))) {
+ // Asset was renamed by git, so rename in DB
+ WIKI.logger.info(`(STORAGE/GIT) Asset marked as renamed: from ${item.oldPath} to ${item.relPath}`)
+
+ const fileHash = assetHelper.generateHash(item.relPath)
+ const assetToRename = await WIKI.models.assets.query().findOne({ hash: fileHash })
+ if (assetToRename) {
+ await WIKI.models.assets.query().patch({
+ filename: item.relPath,
+ hash: fileHash
+ }).findById(assetToRename.id)
+ await assetToRename.deleteAssetCache()
+ } else {
+ WIKI.logger.info(`(STORAGE/GIT) Asset was not found in the DB, nothing to rename: ${item.relPath}`)
+ }
+ continue
+ } else if (!fileExists && ((item.before > 0 && item.after === 0) || (item.deletions > 0 && item.insertions === 0))) {
// Asset was deleted by git, can safely mark as deleted in DB
WIKI.logger.info(`(STORAGE/GIT) Asset marked as deleted: ${item.relPath}`)
@@ -419,7 +452,7 @@ module.exports = {
transform: async (page, enc, cb) => {
const pageObject = await WIKI.models.pages.query().findById(page.id)
page.tags = await pageObject.$relatedQuery('tags')
-
+
let fileName = `${page.path}.${pageHelper.getFileExtension(page.contentType)}`
if (WIKI.config.lang.namespacing && WIKI.config.lang.code !== page.localeCode) {
fileName = `${page.localeCode}/${fileName}`
diff --git a/server/modules/storage/sftp/storage.js b/server/modules/storage/sftp/storage.js
index e1f99af4..49d5ae1d 100644
--- a/server/modules/storage/sftp/storage.js
+++ b/server/modules/storage/sftp/storage.js
@@ -155,7 +155,12 @@ module.exports = {
const folderPaths = _.dropRight(filePath.split('/'))
for (let i = 1; i <= folderPaths.length; i++) {
const folderSection = _.take(folderPaths, i).join('/')
- await this.sftp.mkdir(path.posix.join(this.config.basePath, folderSection))
+ const folderDir = path.posix.join(this.config.basePath, folderSection)
+ try {
+ await this.sftp.readdir(folderDir)
+ } catch (err) {
+ await this.sftp.mkdir(folderDir)
+ }
}
} catch (err) {}
}
diff --git a/yarn.lock b/yarn.lock
index 858f674b..aacf1b04 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -106,18 +106,6 @@
"@algolia/logger-common" "4.5.1"
"@algolia/requester-common" "4.5.1"
-"@aoberoi/passport-slack@1.0.5":
- version "1.0.5"
- resolved "https://registry.yarnpkg.com/@aoberoi/passport-slack/-/passport-slack-1.0.5.tgz#08dcd2d951e94d8e2934bd567c01410a0cc42bec"
- integrity sha1-CNzS2VHpTY4pNL1WfAFBCgzEK+w=
- dependencies:
- babel-polyfill "^6.16.0"
- lodash.defaults "^4.2.0"
- lodash.isfunction "^3.0.8"
- lodash.pickby "^4.6.0"
- needle "^1.4.2"
- passport-oauth2 "^1.3.0"
-
"@apollo/client@^3.1.5":
version "3.2.2"
resolved "https://registry.yarnpkg.com/@apollo/client/-/client-3.2.2.tgz#fe5cad4d53373979f13a925e9da02d8743e798a5"
@@ -5415,15 +5403,6 @@ babel-plugin-transform-imports@2.0.0:
"@babel/types" "^7.4"
is-valid-path "^0.1.1"
-babel-polyfill@^6.16.0:
- version "6.26.0"
- resolved "https://registry.yarnpkg.com/babel-polyfill/-/babel-polyfill-6.26.0.tgz#379937abc67d7895970adc621f284cd966cf2153"
- integrity sha1-N5k3q8Z9eJWXCtxiHyhM2WbPIVM=
- dependencies:
- babel-runtime "^6.26.0"
- core-js "^2.5.0"
- regenerator-runtime "^0.10.5"
-
babel-preset-current-node-syntax@^0.1.3:
version "0.1.3"
resolved "https://registry.yarnpkg.com/babel-preset-current-node-syntax/-/babel-preset-current-node-syntax-0.1.3.tgz#b4b547acddbf963cba555ba9f9cbbb70bfd044da"
@@ -6989,7 +6968,7 @@ core-js@3.6.5, core-js@^3.6.5:
resolved "https://registry.yarnpkg.com/core-js/-/core-js-3.6.5.tgz#7395dc273af37fb2e50e9bd3d9fe841285231d1a"
integrity sha512-vZVEEwZoIsI+vPEuoF9Iqf5H7/M3eeQqWlQnYa8FSKKePuYTf5MWnxb5SDAzCa60b3JBRS5g9b+Dq7b1y/RCrA==
-core-js@^2.4.0, core-js@^2.5.0, core-js@^2.6.5:
+core-js@^2.4.0, core-js@^2.6.5:
version "2.6.9"
resolved "https://registry.yarnpkg.com/core-js/-/core-js-2.6.9.tgz#6b4b214620c834152e179323727fc19741b084f2"
integrity sha512-HOpZf6eXmnl7la+cUdMnLvUxKNqLUzJvgIziQ0DiF3JwSImNphIqdGqzj6hIKyX04MmV0poclQ7+wjWvxQyR2A==
@@ -8012,7 +7991,7 @@ de-indent@^1.0.2:
resolved "https://registry.yarnpkg.com/de-indent/-/de-indent-1.0.2.tgz#b2038e846dc33baa5796128d0804b455b8c1e21d"
integrity sha1-sgOOhG3DO6pXlhKNCAS0VbjB4h0=
-debug@2.6.9, debug@^2.1.2, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
+debug@2.6.9, debug@^2.2.0, debug@^2.3.3, debug@^2.6.9:
version "2.6.9"
resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f"
integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==
@@ -12364,11 +12343,6 @@ lodash.clonedeep@4.5.0:
resolved "https://registry.yarnpkg.com/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz#e23f3f9c4f8fbdde872529c1071857a086e5ccef"
integrity sha1-4j8/nE+Pvd6HJSnBBxhXoIblzO8=
-lodash.defaults@^4.2.0:
- version "4.2.0"
- resolved "https://registry.yarnpkg.com/lodash.defaults/-/lodash.defaults-4.2.0.tgz#d09178716ffea4dde9e5fb7b37f6f0802274580c"
- integrity sha1-0JF4cW/+pN3p5ft7N/bwgCJ0WAw=
-
lodash.includes@^4.3.0:
version "4.3.0"
resolved "https://registry.yarnpkg.com/lodash.includes/-/lodash.includes-4.3.0.tgz#60bb98a87cb923c68ca1e51325483314849f553f"
@@ -12379,11 +12353,6 @@ lodash.isboolean@^3.0.3:
resolved "https://registry.yarnpkg.com/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz#6c2e171db2a257cd96802fd43b01b20d5f5870f6"
integrity sha1-bC4XHbKiV82WgC/UOwGyDV9YcPY=
-lodash.isfunction@^3.0.8:
- version "3.0.9"
- resolved "https://registry.yarnpkg.com/lodash.isfunction/-/lodash.isfunction-3.0.9.tgz#06de25df4db327ac931981d1bdb067e5af68d051"
- integrity sha512-AirXNj15uRIMMPihnkInB4i3NHeb4iBtNg9WRWuK2o31S+ePwwNmDPaTL3o7dTJ+VXNZim7rFs4rxN4YU1oUJw==
-
lodash.isinteger@^4.0.4:
version "4.0.4"
resolved "https://registry.yarnpkg.com/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz#619c0af3d03f8b04c31f5882840b77b11cd68343"
@@ -12419,11 +12388,6 @@ lodash.once@^4.0.0, lodash.once@^4.1.1:
resolved "https://registry.yarnpkg.com/lodash.once/-/lodash.once-4.1.1.tgz#0dd3971213c7c56df880977d504c88fb471a97ac"
integrity sha1-DdOXEhPHxW34gJd9UEyI+0cal6w=
-lodash.pickby@^4.6.0:
- version "4.6.0"
- resolved "https://registry.yarnpkg.com/lodash.pickby/-/lodash.pickby-4.6.0.tgz#7dea21d8c18d7703a27c704c15d3b84a67e33aff"
- integrity sha1-feoh2MGNdwOifHBMFdO4SmfjOv8=
-
lodash.repeat@^4.0.0:
version "4.1.0"
resolved "https://registry.yarnpkg.com/lodash.repeat/-/lodash.repeat-4.1.0.tgz#fc7de8131d8c8ac07e4b49f74ffe829d1f2bec44"
@@ -13299,14 +13263,6 @@ ncp@~2.0.0:
resolved "https://registry.yarnpkg.com/ncp/-/ncp-2.0.0.tgz#195a21d6c46e361d2fb1281ba38b91e9df7bdbb3"
integrity sha1-GVoh1sRuNh0vsSgbo4uR6d9727M=
-needle@^1.4.2:
- version "1.6.0"
- resolved "https://registry.yarnpkg.com/needle/-/needle-1.6.0.tgz#f52a5858972121618e002f8e6384cadac22d624f"
- integrity sha1-9SpYWJchIWGOAC+OY4TK2sItYk8=
- dependencies:
- debug "^2.1.2"
- iconv-lite "^0.4.4"
-
needle@^2.2.1:
version "2.4.0"
resolved "https://registry.yarnpkg.com/needle/-/needle-2.4.0.tgz#6833e74975c444642590e15a750288c5f939b57c"
@@ -14298,7 +14254,7 @@ passport-oauth2@1.2.0:
passport-strategy "1.x.x"
uid2 "0.0.x"
-passport-oauth2@1.5.0, passport-oauth2@1.x.x, passport-oauth2@^1.2.0, passport-oauth2@^1.3.0, passport-oauth2@^1.4.0, passport-oauth2@^1.5.0:
+passport-oauth2@1.5.0, passport-oauth2@1.x.x, passport-oauth2@^1.2.0, passport-oauth2@^1.4.0, passport-oauth2@^1.5.0:
version "1.5.0"
resolved "https://registry.yarnpkg.com/passport-oauth2/-/passport-oauth2-1.5.0.tgz#64babbb54ac46a4dcab35e7f266ed5294e3c4108"
integrity sha512-kqBt6vR/5VlCK8iCx1/KpY42kQ+NEHZwsSyt4Y6STiNjU+wWICG1i8ucc1FapXDGO15C5O5VZz7+7vRzrDPXXQ==
@@ -14350,6 +14306,14 @@ passport-saml@1.3.5:
xmlbuilder "^11.0.0"
xmldom "0.1.x"
+passport-slack-oauth2@1.1.1:
+ version "1.1.1"
+ resolved "https://registry.yarnpkg.com/passport-slack-oauth2/-/passport-slack-oauth2-1.1.1.tgz#d831ffc3f1e968fcc3622e6ecf41643c8d8f9cbc"
+ integrity sha512-xC+yMKFXximP5TzSNt4lr9TP78MMos5B+acC7bJNCxBAVNyL9e02AEpVpVtyMIqHv4nNZnv1vyoOb50J8VCcZQ==
+ dependencies:
+ passport-oauth2 "^1.5.0"
+ pkginfo "^0.4.1"
+
passport-strategy@*, passport-strategy@1.x.x, passport-strategy@^1.0.0:
version "1.0.0"
resolved "https://registry.yarnpkg.com/passport-strategy/-/passport-strategy-1.0.0.tgz#b5539aa8fc225a3d1ad179476ddf236b440f52e4"
@@ -14732,6 +14696,11 @@ pkginfo@0.2.x, pkginfo@^0.2.3:
resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.2.3.tgz#7239c42a5ef6c30b8f328439d9b9ff71042490f8"
integrity sha1-cjnEKl72wwuPMoQ52bn/cQQkkPg=
+pkginfo@^0.4.1:
+ version "0.4.1"
+ resolved "https://registry.yarnpkg.com/pkginfo/-/pkginfo-0.4.1.tgz#b5418ef0439de5425fc4995042dced14fb2a84ff"
+ integrity sha1-tUGO8EOd5UJfxJlQQtztFPsqhP8=
+
pleeease-filters@^4.0.0:
version "4.0.0"
resolved "https://registry.yarnpkg.com/pleeease-filters/-/pleeease-filters-4.0.0.tgz#6632b2fb05648d2758d865384fbced79e1ccaec7"
@@ -16479,11 +16448,6 @@ regenerate@^1.4.0:
resolved "https://registry.yarnpkg.com/regenerate/-/regenerate-1.4.0.tgz#4a856ec4b56e4077c557589cae85e7a4c8869a11"
integrity sha512-1G6jJVDWrt0rK99kBjvEtziZNCICAuvIPkSiUFIQxVP06RCVpq3dmDo2oi6ABpYaDYaTRr67BEhL8r1wgEZZKg==
-regenerator-runtime@^0.10.5:
- version "0.10.5"
- resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.10.5.tgz#336c3efc1220adcedda2c9fab67b5a7955a33658"
- integrity sha1-M2w+/BIgrc7dosn6tntaeVWjNlg=
-
regenerator-runtime@^0.11.0:
version "0.11.1"
resolved "https://registry.yarnpkg.com/regenerator-runtime/-/regenerator-runtime-0.11.1.tgz#be05ad7f9bf7d22e056f9726cee5017fbf19e2e9"
|