wiki-server 0.26.0-rc.0 → 0.26.0-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/page.js +87 -99
- package/lib/plugins.js +6 -6
- package/lib/search.js +11 -7
- package/lib/security.js +2 -2
- package/lib/server.js +109 -122
- package/lib/sitemap.js +6 -6
- package/package.json +2 -5
package/lib/page.js
CHANGED
|
@@ -13,9 +13,6 @@
|
|
|
13
13
|
const fs = require('fs')
|
|
14
14
|
const path = require('path')
|
|
15
15
|
const events = require('events')
|
|
16
|
-
const glob = require('glob')
|
|
17
|
-
|
|
18
|
-
const async = require('async')
|
|
19
16
|
|
|
20
17
|
const random_id = require('./random_id')
|
|
21
18
|
const synopsis = require('wiki-client/lib/synopsis')
|
|
@@ -35,6 +32,25 @@ module.exports = exports = argv => {
|
|
|
35
32
|
if (e) throw e
|
|
36
33
|
})
|
|
37
34
|
|
|
35
|
+
// create a list of plugin pages.
|
|
36
|
+
const pluginPages = new Map()
|
|
37
|
+
Object.keys(require.main.require('./package').dependencies)
|
|
38
|
+
.filter(depend => depend.startsWith('wiki-plugin'))
|
|
39
|
+
.forEach(plugin => {
|
|
40
|
+
const pagesPath = path.join(
|
|
41
|
+
path.dirname(require.resolve(`${plugin}/package`, { paths: require.main.paths })),
|
|
42
|
+
'pages',
|
|
43
|
+
)
|
|
44
|
+
fs.readdir(pagesPath, { withFileTypes: true }, (err, entries) => {
|
|
45
|
+
if (err) return
|
|
46
|
+
entries.forEach(entry => {
|
|
47
|
+
if (entry.isFile() && !pluginPages.has(entry.name)) {
|
|
48
|
+
pluginPages.set(entry.name, { pluginName: plugin, pluginPath: entry.parentPath })
|
|
49
|
+
}
|
|
50
|
+
})
|
|
51
|
+
})
|
|
52
|
+
})
|
|
53
|
+
|
|
38
54
|
// #### Private utility methods. ####
|
|
39
55
|
const load_parse = (loc, cb, annotations = {}) => {
|
|
40
56
|
let page
|
|
@@ -46,14 +62,13 @@ module.exports = exports = argv => {
|
|
|
46
62
|
const errorPage = path.basename(loc)
|
|
47
63
|
const errorPagePath = path.dirname(loc)
|
|
48
64
|
const recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
if (exists) {
|
|
65
|
+
fs.access(path.dirname(recyclePage), fs.constants.F_OK, err => {
|
|
66
|
+
if (!err) {
|
|
52
67
|
fs.rename(loc, recyclePage, err => {
|
|
53
68
|
if (err) {
|
|
54
|
-
console.log(
|
|
69
|
+
console.log(`ERROR: moving problem page ${loc} to recycler`, err)
|
|
55
70
|
} else {
|
|
56
|
-
console.log(
|
|
71
|
+
console.log(`ERROR: problem page ${loc} moved to recycler`)
|
|
57
72
|
}
|
|
58
73
|
})
|
|
59
74
|
} else {
|
|
@@ -63,9 +78,9 @@ module.exports = exports = argv => {
|
|
|
63
78
|
} else {
|
|
64
79
|
fs.rename(loc, recyclePage, err => {
|
|
65
80
|
if (err) {
|
|
66
|
-
console.log(
|
|
81
|
+
console.log(`ERROR: moving problem page ${loc} to recycler`, err)
|
|
67
82
|
} else {
|
|
68
|
-
console.log(
|
|
83
|
+
console.log(`ERROR: problem page ${loc} moved to recycler`)
|
|
69
84
|
}
|
|
70
85
|
})
|
|
71
86
|
}
|
|
@@ -104,9 +119,8 @@ module.exports = exports = argv => {
|
|
|
104
119
|
const tryDefaults = (file, cb) => {
|
|
105
120
|
const lastDefault = cb => {
|
|
106
121
|
const defloc = path.join(argv.root, 'default-data', 'pages', file)
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
if (exists) {
|
|
122
|
+
fs.access(defloc, fs.constants.F_OK, err => {
|
|
123
|
+
if (!err) {
|
|
110
124
|
cb(defloc)
|
|
111
125
|
} else {
|
|
112
126
|
cb(null)
|
|
@@ -115,10 +129,8 @@ module.exports = exports = argv => {
|
|
|
115
129
|
}
|
|
116
130
|
if (argv.defaults) {
|
|
117
131
|
const defloc = path.join(argv.data, '..', argv.defaults, 'pages', file)
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
fs.exists(defloc, exists => {
|
|
121
|
-
if (exists) {
|
|
132
|
+
fs.access(defloc, fs.constants.F_OK, err => {
|
|
133
|
+
if (!err) {
|
|
122
134
|
cb(defloc)
|
|
123
135
|
} else {
|
|
124
136
|
lastDefault(cb)
|
|
@@ -138,22 +150,19 @@ module.exports = exports = argv => {
|
|
|
138
150
|
case 'delete':
|
|
139
151
|
if (file.startsWith('recycler/')) {
|
|
140
152
|
// delete from recycler
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
if (exists)
|
|
153
|
+
fs.access(loc, fs.constants.F_OK, err => {
|
|
154
|
+
if (!err)
|
|
144
155
|
fs.unlink(loc, err => {
|
|
145
156
|
cb(err)
|
|
146
157
|
})
|
|
147
158
|
})
|
|
148
159
|
} else {
|
|
149
160
|
// move page to recycler
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
if (exists) {
|
|
161
|
+
fs.access(loc, fs.constants.F_OK, err => {
|
|
162
|
+
if (!err) {
|
|
153
163
|
const recycleLoc = path.join(argv.recycler, file)
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
if (exists) {
|
|
164
|
+
fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => {
|
|
165
|
+
if (!err) {
|
|
157
166
|
fs.rename(loc, recycleLoc, err => {
|
|
158
167
|
cb(err)
|
|
159
168
|
})
|
|
@@ -203,13 +212,11 @@ module.exports = exports = argv => {
|
|
|
203
212
|
return
|
|
204
213
|
}
|
|
205
214
|
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
if (exists) {
|
|
215
|
+
fs.access(loc, fs.constants.F_OK, err => {
|
|
216
|
+
if (!err) {
|
|
209
217
|
const recycleLoc = path.join(argv.recycler, file)
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
if (exists) {
|
|
218
|
+
fs.access(path.dirname(recycleLoc), fs.constants.F_OK, err => {
|
|
219
|
+
if (!err) {
|
|
213
220
|
copyFile(loc, recycleLoc, err => {
|
|
214
221
|
cb(err)
|
|
215
222
|
})
|
|
@@ -229,42 +236,20 @@ module.exports = exports = argv => {
|
|
|
229
236
|
break
|
|
230
237
|
}
|
|
231
238
|
case 'get':
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
if (exists) {
|
|
239
|
+
fs.access(loc, fs.constants.F_OK, err => {
|
|
240
|
+
if (!err) {
|
|
235
241
|
load_parse(loc, cb, { plugin: undefined })
|
|
236
242
|
} else {
|
|
237
243
|
tryDefaults(file, defloc => {
|
|
238
244
|
if (defloc) {
|
|
239
245
|
load_parse(defloc, cb)
|
|
240
246
|
} else {
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
let count = plugins.length
|
|
250
|
-
const giveUp = () => {
|
|
251
|
-
count -= 1
|
|
252
|
-
if (count === 0) {
|
|
253
|
-
cb(null, 'Page not found', 404)
|
|
254
|
-
}
|
|
255
|
-
}
|
|
256
|
-
plugins.forEach(plugin => {
|
|
257
|
-
const pluginName = plugin.slice(12, -6)
|
|
258
|
-
const pluginloc = path.join(argv.packageDir, plugin, file)
|
|
259
|
-
fs.exists(pluginloc, exists => {
|
|
260
|
-
if (exists) {
|
|
261
|
-
load_parse(pluginloc, cb, { plugin: pluginName })
|
|
262
|
-
} else {
|
|
263
|
-
giveUp()
|
|
264
|
-
}
|
|
265
|
-
})
|
|
266
|
-
})
|
|
267
|
-
})
|
|
247
|
+
if (pluginPages.has(file)) {
|
|
248
|
+
const { pluginName, pluginPath } = pluginPages.get(file)
|
|
249
|
+
load_parse(path.join(pluginPath, file), cb, { plugin: pluginName.slice(12) })
|
|
250
|
+
} else {
|
|
251
|
+
cb(null, 'Page not found', 404)
|
|
252
|
+
}
|
|
268
253
|
}
|
|
269
254
|
})
|
|
270
255
|
}
|
|
@@ -272,11 +257,11 @@ module.exports = exports = argv => {
|
|
|
272
257
|
break
|
|
273
258
|
case 'put':
|
|
274
259
|
page = JSON.stringify(page, null, 2)
|
|
275
|
-
fs.
|
|
276
|
-
if (
|
|
260
|
+
fs.access(path.dirname(loc), fs.constants.F_OK, err => {
|
|
261
|
+
if (!err) {
|
|
277
262
|
fs.writeFile(loc, page, err => {
|
|
278
263
|
if (err) {
|
|
279
|
-
console.log(
|
|
264
|
+
console.log(`ERROR: write file ${loc} `, err)
|
|
280
265
|
}
|
|
281
266
|
cb(err)
|
|
282
267
|
})
|
|
@@ -285,7 +270,7 @@ module.exports = exports = argv => {
|
|
|
285
270
|
if (err) cb(err)
|
|
286
271
|
fs.writeFile(loc, page, err => {
|
|
287
272
|
if (err) {
|
|
288
|
-
console.log(
|
|
273
|
+
console.log(`ERROR: write file ${loc} `, err)
|
|
289
274
|
}
|
|
290
275
|
cb(err)
|
|
291
276
|
})
|
|
@@ -294,7 +279,7 @@ module.exports = exports = argv => {
|
|
|
294
279
|
})
|
|
295
280
|
break
|
|
296
281
|
default:
|
|
297
|
-
console.log(
|
|
282
|
+
console.log(`pagehandler: unrecognized action ${action}`)
|
|
298
283
|
}
|
|
299
284
|
}
|
|
300
285
|
|
|
@@ -392,43 +377,46 @@ module.exports = exports = argv => {
|
|
|
392
377
|
}
|
|
393
378
|
return collaborativeLinks
|
|
394
379
|
}
|
|
380
|
+
|
|
395
381
|
fs.readdir(argv.db, (e, files) => {
|
|
396
382
|
if (e) return cb(e)
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
383
|
+
const doSitemap = async file => {
|
|
384
|
+
return new Promise(resolve => {
|
|
385
|
+
itself.get(file, (e, page, status) => {
|
|
386
|
+
if (file.match(/^\./)) return resolve(null)
|
|
387
|
+
if (e || status === 404) {
|
|
388
|
+
console.log('Problem building sitemap:', file, 'e: ', e, 'status:', status)
|
|
389
|
+
return resolve(null) // Ignore errors in the pagehandler get.
|
|
390
|
+
}
|
|
391
|
+
let pageLinksMap
|
|
392
|
+
try {
|
|
393
|
+
pageLinksMap = page.story.reduce(extractPageLinks, new Map())
|
|
394
|
+
} catch (err) {
|
|
395
|
+
console.log(`METADATA *** ${wikiName} reduce to extract links on ${file} failed`, err.message)
|
|
396
|
+
pageLinksMap = []
|
|
397
|
+
}
|
|
398
|
+
//
|
|
399
|
+
const pageLinks = pageLinksMap.size > 0 ? Object.fromEntries(pageLinksMap) : undefined
|
|
400
|
+
|
|
401
|
+
resolve({
|
|
402
|
+
slug: file,
|
|
403
|
+
title: page.title,
|
|
404
|
+
date: editDate(page.journal),
|
|
405
|
+
synopsis: synopsis(page),
|
|
406
|
+
links: pageLinks,
|
|
407
|
+
})
|
|
422
408
|
})
|
|
423
409
|
})
|
|
424
410
|
}
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
411
|
+
|
|
412
|
+
Promise.all(files.map(doSitemap))
|
|
413
|
+
.then(sitemap => {
|
|
414
|
+
cb(
|
|
415
|
+
null,
|
|
416
|
+
sitemap.filter(item => item != null),
|
|
417
|
+
)
|
|
418
|
+
})
|
|
419
|
+
.catch(e => cb(e))
|
|
432
420
|
})
|
|
433
421
|
}
|
|
434
422
|
|
package/lib/plugins.js
CHANGED
|
@@ -9,7 +9,6 @@
|
|
|
9
9
|
// support server-side plugins
|
|
10
10
|
|
|
11
11
|
const fs = require('node:fs')
|
|
12
|
-
const glob = require('glob')
|
|
13
12
|
const { pathToFileURL } = require('node:url')
|
|
14
13
|
// forward = require './forward'
|
|
15
14
|
|
|
@@ -24,8 +23,8 @@ module.exports = exports = argv => {
|
|
|
24
23
|
|
|
25
24
|
const startServer = (params, plugin) => {
|
|
26
25
|
const server = `${argv.packageDir}/${plugin}/server/server.js`
|
|
27
|
-
fs.
|
|
28
|
-
if (
|
|
26
|
+
fs.access(server, fs.constants.F_OK, err => {
|
|
27
|
+
if (!err) {
|
|
29
28
|
console.log('starting plugin', plugin)
|
|
30
29
|
import(pathToFileURL(server))
|
|
31
30
|
.then(exported => {
|
|
@@ -43,11 +42,12 @@ module.exports = exports = argv => {
|
|
|
43
42
|
// emitter = new events.EventEmitter()
|
|
44
43
|
// forward.init params.app, emitter
|
|
45
44
|
// params.emitter = emitter
|
|
46
|
-
|
|
47
|
-
|
|
45
|
+
|
|
46
|
+
Object.keys(require.main.require('./package').dependencies)
|
|
47
|
+
.filter(depend => depend.startsWith('wiki-plugin'))
|
|
48
|
+
.forEach(plugin => {
|
|
48
49
|
startServer(params, plugin)
|
|
49
50
|
})
|
|
50
|
-
})
|
|
51
51
|
}
|
|
52
52
|
|
|
53
53
|
return { startServers }
|
package/lib/search.js
CHANGED
|
@@ -85,8 +85,8 @@ module.exports = exports = argv => {
|
|
|
85
85
|
|
|
86
86
|
const searchSave = (siteIndex, cb) => {
|
|
87
87
|
// save index to file
|
|
88
|
-
fs.
|
|
89
|
-
if (
|
|
88
|
+
fs.access(argv.status, fs.constants.F_OK, err => {
|
|
89
|
+
if (!err) {
|
|
90
90
|
writeFileAtomic(siteIndexLoc, JSON.stringify(siteIndex), e => {
|
|
91
91
|
if (e) return cb(e)
|
|
92
92
|
touch(indexUpdateFlag, () => {
|
|
@@ -108,8 +108,8 @@ module.exports = exports = argv => {
|
|
|
108
108
|
|
|
109
109
|
const searchRestore = cb => {
|
|
110
110
|
// restore index, or create if it doesn't already exist
|
|
111
|
-
fs.
|
|
112
|
-
if (
|
|
111
|
+
fs.access(siteIndexLoc, fs.constants.F_OK, err => {
|
|
112
|
+
if (!err) {
|
|
113
113
|
fs.readFile(siteIndexLoc, (err, data) => {
|
|
114
114
|
if (err) return cb(err)
|
|
115
115
|
try {
|
|
@@ -169,8 +169,12 @@ module.exports = exports = argv => {
|
|
|
169
169
|
.replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ')
|
|
170
170
|
.replace(/<(?:[^>])+>/g, ' ')
|
|
171
171
|
.replace(/(https?:.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, match => {
|
|
172
|
-
|
|
173
|
-
|
|
172
|
+
try {
|
|
173
|
+
const myUrl = new URL(match)
|
|
174
|
+
return myUrl.hostname
|
|
175
|
+
} catch {
|
|
176
|
+
return ' '
|
|
177
|
+
}
|
|
174
178
|
})
|
|
175
179
|
.replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ')
|
|
176
180
|
.replace(/[\p{White_Space}\n\t]+/gu, ' ')
|
|
@@ -250,7 +254,7 @@ module.exports = exports = argv => {
|
|
|
250
254
|
// we save the pagehandler, so we can recreate the site index if it is removed
|
|
251
255
|
searchPageHandler = searchPageHandler ?? pagehandler
|
|
252
256
|
|
|
253
|
-
//timeLabel =
|
|
257
|
+
//timeLabel = `SITE INDEX ${wikiName} : Created`
|
|
254
258
|
//console.time timeLabel
|
|
255
259
|
|
|
256
260
|
pagehandler.slugs((e, slugs) => {
|
package/lib/security.js
CHANGED
|
@@ -41,8 +41,8 @@ module.exports = exports = (log, loga, argv) => {
|
|
|
41
41
|
|
|
42
42
|
// Retrieve owner infomation from identity file in status directory
|
|
43
43
|
security.retrieveOwner = cb => {
|
|
44
|
-
fs.
|
|
45
|
-
if (
|
|
44
|
+
fs.access(idFile, fs.constants.F_OK, err => {
|
|
45
|
+
if (!err) {
|
|
46
46
|
fs.readFile(idFile, (err, data) => {
|
|
47
47
|
if (err) return cb(err)
|
|
48
48
|
owner += data
|
package/lib/server.js
CHANGED
|
@@ -28,8 +28,6 @@ const { pipeline } = require('node:stream/promises')
|
|
|
28
28
|
// From npm
|
|
29
29
|
const express = require('express')
|
|
30
30
|
const hbs = require('express-hbs')
|
|
31
|
-
const glob = require('glob')
|
|
32
|
-
const async = require('async')
|
|
33
31
|
const f = require('flates')
|
|
34
32
|
|
|
35
33
|
const createDOMPurify = require('dompurify')
|
|
@@ -38,8 +36,7 @@ const { JSDOM } = require('jsdom')
|
|
|
38
36
|
const window = new JSDOM('').window
|
|
39
37
|
const DOMPurify = createDOMPurify(window)
|
|
40
38
|
|
|
41
|
-
//
|
|
42
|
-
const fetch = (...args) => import('node-fetch').then(({ default: fetch }) => fetch(...args))
|
|
39
|
+
// Using native fetch API (available in Node.js 18+)
|
|
43
40
|
|
|
44
41
|
// Express 4 middleware
|
|
45
42
|
const logger = require('morgan')
|
|
@@ -163,7 +160,7 @@ module.exports = exports = argv => {
|
|
|
163
160
|
|
|
164
161
|
const remoteGet = (remote, slug, cb) => {
|
|
165
162
|
// assume http, as we know no better at this point and we need to specify a protocol.
|
|
166
|
-
const remoteURL = new URL(
|
|
163
|
+
const remoteURL = new URL(`http://${remote}/${slug}.json`).toString()
|
|
167
164
|
// set a two second timeout
|
|
168
165
|
fetch(remoteURL, { signal: AbortSignal.timeout(2000) })
|
|
169
166
|
.then(res => {
|
|
@@ -271,13 +268,16 @@ module.exports = exports = argv => {
|
|
|
271
268
|
app.use('/assets', cors, express.static(argv.assets))
|
|
272
269
|
|
|
273
270
|
// Add static routes to the plugins client.
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
const
|
|
278
|
-
|
|
271
|
+
Object.keys(require.main.require('./package').dependencies)
|
|
272
|
+
.filter(depend => depend.startsWith('wiki-plugin'))
|
|
273
|
+
.forEach(plugin => {
|
|
274
|
+
const clientPath = path.join(
|
|
275
|
+
path.dirname(require.resolve(`${plugin}/package`, { paths: require.main.paths })),
|
|
276
|
+
'client',
|
|
277
|
+
)
|
|
278
|
+
const pluginPath = '/plugins/' + plugin.slice(12)
|
|
279
|
+
app.use(pluginPath, cors, express.static(clientPath, staticPathOptions))
|
|
279
280
|
})
|
|
280
|
-
})
|
|
281
281
|
|
|
282
282
|
// Add static routes to the security client.
|
|
283
283
|
if (argv.security != './security') {
|
|
@@ -387,32 +387,17 @@ module.exports = exports = argv => {
|
|
|
387
387
|
app.get('/system/factories.json', (req, res) => {
|
|
388
388
|
res.status(200)
|
|
389
389
|
res.header('Content-Type', 'application/json')
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
if (err) {
|
|
399
|
-
return cb()
|
|
400
|
-
}
|
|
401
|
-
try {
|
|
402
|
-
const factory = JSON.parse(data)
|
|
403
|
-
cb(null, factory)
|
|
404
|
-
} catch (err) {
|
|
405
|
-
return cb()
|
|
406
|
-
}
|
|
407
|
-
})
|
|
408
|
-
}
|
|
409
|
-
async.map(files, doFactories, (e, factories) => {
|
|
410
|
-
if (e) {
|
|
411
|
-
res.e(e)
|
|
390
|
+
const factories = []
|
|
391
|
+
Object.keys(require.main.require('./package').dependencies)
|
|
392
|
+
.filter(depend => depend.startsWith('wiki-plugin'))
|
|
393
|
+
.forEach(plugin => {
|
|
394
|
+
try {
|
|
395
|
+
factories.push(require.main.require(`${plugin}/factory`))
|
|
396
|
+
} catch {
|
|
397
|
+
// do nothing if plugin doesn't have a factory category.
|
|
412
398
|
}
|
|
413
|
-
res.end(JSON.stringify(factories))
|
|
414
399
|
})
|
|
415
|
-
|
|
400
|
+
res.end(JSON.stringify(factories))
|
|
416
401
|
})
|
|
417
402
|
|
|
418
403
|
// ###### Json Routes ######
|
|
@@ -444,7 +429,7 @@ module.exports = exports = argv => {
|
|
|
444
429
|
// If themes doesn't exist send 404 and let the client
|
|
445
430
|
// deal with it.
|
|
446
431
|
app.get(/^\/theme\/(\w+\.\w+)$/, cors, (req, res) => {
|
|
447
|
-
res.sendFile(path.join(argv.status, 'theme', req.params[0]), e => {
|
|
432
|
+
res.sendFile(path.join(argv.status, 'theme', req.params[0]), { dotfiles: 'allow' }, e => {
|
|
448
433
|
if (e) {
|
|
449
434
|
// swallow the error if the theme does not exist...
|
|
450
435
|
if (req.path === '/theme/style.css') {
|
|
@@ -462,9 +447,9 @@ module.exports = exports = argv => {
|
|
|
462
447
|
const favLoc = path.join(argv.status, 'favicon.png')
|
|
463
448
|
const defaultFavLoc = path.join(argv.root, 'default-data', 'status', 'favicon.png')
|
|
464
449
|
app.get('/favicon.png', cors, (req, res) => {
|
|
465
|
-
fs.
|
|
466
|
-
if (
|
|
467
|
-
res.sendFile(favLoc)
|
|
450
|
+
fs.access(favLoc, fs.constants.F_OK, err => {
|
|
451
|
+
if (!err) {
|
|
452
|
+
res.sendFile(favLoc, { dotfiles: 'allow' })
|
|
468
453
|
} else {
|
|
469
454
|
res.sendFile(defaultFavLoc)
|
|
470
455
|
}
|
|
@@ -484,9 +469,9 @@ module.exports = exports = argv => {
|
|
|
484
469
|
// save it.
|
|
485
470
|
app.post('/favicon.png', authorized, (req, res) => {
|
|
486
471
|
const favicon = req.body.image.replace(/^data:image\/png;base64,/, '')
|
|
487
|
-
const buf =
|
|
488
|
-
fs.
|
|
489
|
-
if (
|
|
472
|
+
const buf = Buffer.from(favicon, 'base64')
|
|
473
|
+
fs.access(argv.status, fs.constants.F_OK, err => {
|
|
474
|
+
if (!err) {
|
|
490
475
|
fs.writeFile(favLoc, buf, e => {
|
|
491
476
|
if (e) {
|
|
492
477
|
return res.e(e)
|
|
@@ -508,7 +493,7 @@ module.exports = exports = argv => {
|
|
|
508
493
|
|
|
509
494
|
// Redirect remote favicons to the server they are needed from.
|
|
510
495
|
app.get(/^\/remote\/([a-zA-Z0-9:.-]+\/favicon.png)$/, (req, res) => {
|
|
511
|
-
const remotefav =
|
|
496
|
+
const remotefav = `http://${req.params[0]}`
|
|
512
497
|
res.redirect(remotefav)
|
|
513
498
|
})
|
|
514
499
|
|
|
@@ -519,38 +504,40 @@ module.exports = exports = argv => {
|
|
|
519
504
|
// negative space outward pointing arrows nicely indicates that items can be removed
|
|
520
505
|
const recyclerFavLoc = path.join(argv.root, 'default-data', 'status', 'recycler.png')
|
|
521
506
|
app.get('/recycler/favicon.png', authorized, (req, res) => {
|
|
522
|
-
res.sendFile(recyclerFavLoc)
|
|
507
|
+
res.sendFile(recyclerFavLoc, { dotfiles: 'allow' })
|
|
523
508
|
})
|
|
524
509
|
|
|
525
510
|
// Send an array of pages currently in the recycler via json
|
|
526
511
|
app.get('/recycler/system/slugs.json', authorized, (req, res) => {
|
|
527
512
|
fs.readdir(argv.recycler, (e, files) => {
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
513
|
+
if (e) {
|
|
514
|
+
return res.e(e)
|
|
515
|
+
}
|
|
516
|
+
const doRecyclermap = async file => {
|
|
517
|
+
return new Promise(resolve => {
|
|
518
|
+
const recycleFile = 'recycler/' + file
|
|
519
|
+
pagehandler.get(recycleFile, (e, page, status) => {
|
|
520
|
+
if (e || status === 404) {
|
|
521
|
+
console.log('Problem building recycler map:', file, 'e: ', e)
|
|
522
|
+
// this will leave an undefined/empty item in the array, which we will filter out later
|
|
523
|
+
return resolve(null)
|
|
524
|
+
}
|
|
525
|
+
resolve({
|
|
526
|
+
slug: file,
|
|
527
|
+
title: page.title,
|
|
528
|
+
})
|
|
539
529
|
})
|
|
540
530
|
})
|
|
541
531
|
}
|
|
542
532
|
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
recyclermap = recyclermap.filter(el => !!el)
|
|
552
|
-
res.send(recyclermap)
|
|
553
|
-
})
|
|
533
|
+
Promise.all(files.map(doRecyclermap))
|
|
534
|
+
.then(recyclermap => {
|
|
535
|
+
recyclermap = recyclermap.filter(el => !!el)
|
|
536
|
+
res.send(recyclermap)
|
|
537
|
+
})
|
|
538
|
+
.catch(error => {
|
|
539
|
+
res.e(error)
|
|
540
|
+
})
|
|
554
541
|
})
|
|
555
542
|
})
|
|
556
543
|
|
|
@@ -590,21 +577,21 @@ module.exports = exports = argv => {
|
|
|
590
577
|
|
|
591
578
|
// Returns a list of installed plugins. (does this get called anymore!)
|
|
592
579
|
app.get('/system/plugins.json', cors, (req, res) => {
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
res.
|
|
600
|
-
}
|
|
580
|
+
try {
|
|
581
|
+
const pluginNames = Object.keys(require.main.require('./package').dependencies)
|
|
582
|
+
.filter(depend => depend.startsWith('wiki-plugin'))
|
|
583
|
+
.map(name => name.slice(12))
|
|
584
|
+
res.send(pluginNames)
|
|
585
|
+
} catch (e) {
|
|
586
|
+
return res.e(e)
|
|
587
|
+
}
|
|
601
588
|
})
|
|
602
589
|
//{
|
|
603
590
|
const sitemapLoc = path.join(argv.status, 'sitemap.json')
|
|
604
591
|
app.get('/system/sitemap.json', cors, (req, res) => {
|
|
605
|
-
fs.
|
|
606
|
-
if (
|
|
607
|
-
res.sendFile(sitemapLoc)
|
|
592
|
+
fs.access(sitemapLoc, fs.constants.F_OK, err => {
|
|
593
|
+
if (!err) {
|
|
594
|
+
res.sendFile(sitemapLoc, { dotfiles: 'allow' })
|
|
608
595
|
} else {
|
|
609
596
|
// only createSitemap if we are not already creating one
|
|
610
597
|
if (!sitemaphandler.isWorking()) {
|
|
@@ -612,7 +599,7 @@ module.exports = exports = argv => {
|
|
|
612
599
|
}
|
|
613
600
|
// wait for the sitemap file to be written, before sending
|
|
614
601
|
sitemaphandler.once('finished', () => {
|
|
615
|
-
res.sendFile(sitemapLoc)
|
|
602
|
+
res.sendFile(sitemapLoc, { dotfiles: 'allow' })
|
|
616
603
|
})
|
|
617
604
|
}
|
|
618
605
|
})
|
|
@@ -620,15 +607,15 @@ module.exports = exports = argv => {
|
|
|
620
607
|
|
|
621
608
|
const xmlSitemapLoc = path.join(argv.status, 'sitemap.xml')
|
|
622
609
|
app.get('/sitemap.xml', cors, (req, res) => {
|
|
623
|
-
fs.
|
|
624
|
-
if (
|
|
625
|
-
res.sendFile(xmlSitemapLoc)
|
|
610
|
+
fs.access(sitemapLoc, fs.constants.F_OK, err => {
|
|
611
|
+
if (!err) {
|
|
612
|
+
res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' })
|
|
626
613
|
} else {
|
|
627
614
|
if (!sitemaphandler.isWorking()) {
|
|
628
615
|
sitemaphandler.createSitemap(pagehandler)
|
|
629
616
|
}
|
|
630
617
|
sitemaphandler.once('finished', () => {
|
|
631
|
-
res.sendFile(xmlSitemapLoc)
|
|
618
|
+
res.sendFile(xmlSitemapLoc, { dotfiles: 'allow' })
|
|
632
619
|
})
|
|
633
620
|
}
|
|
634
621
|
})
|
|
@@ -636,16 +623,16 @@ module.exports = exports = argv => {
|
|
|
636
623
|
|
|
637
624
|
const searchIndexLoc = path.join(argv.status, 'site-index.json')
|
|
638
625
|
app.get('/system/site-index.json', cors, (req, res) => {
|
|
639
|
-
fs.
|
|
640
|
-
if (
|
|
641
|
-
res.sendFile(searchIndexLoc)
|
|
626
|
+
fs.access(searchIndexLoc, fs.constants.F_OK, err => {
|
|
627
|
+
if (!err) {
|
|
628
|
+
res.sendFile(searchIndexLoc, { dotfiles: 'allow' })
|
|
642
629
|
} else {
|
|
643
630
|
// only create index if we are not already creating one
|
|
644
631
|
if (!searchhandler.isWorking()) {
|
|
645
632
|
searchhandler.createIndex(pagehandler)
|
|
646
633
|
}
|
|
647
634
|
searchhandler.once('indexed', () => {
|
|
648
|
-
res.sendFile(searchIndexLoc)
|
|
635
|
+
res.sendFile(searchIndexLoc, { dotfiles: 'allow' })
|
|
649
636
|
})
|
|
650
637
|
}
|
|
651
638
|
})
|
|
@@ -656,28 +643,29 @@ module.exports = exports = argv => {
|
|
|
656
643
|
if (e) {
|
|
657
644
|
return res.e(e)
|
|
658
645
|
}
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
(stub, done) => {
|
|
646
|
+
const pagePromises = sitemap.map(stub => {
|
|
647
|
+
return new Promise((resolve, reject) => {
|
|
662
648
|
pagehandler.get(stub.slug, (error, page) => {
|
|
663
|
-
if (
|
|
664
|
-
return
|
|
649
|
+
if (error) {
|
|
650
|
+
return reject(error)
|
|
665
651
|
}
|
|
666
|
-
|
|
652
|
+
resolve({ slug: stub.slug, page })
|
|
667
653
|
})
|
|
668
|
-
}
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
)
|
|
679
|
-
}
|
|
680
|
-
|
|
654
|
+
})
|
|
655
|
+
})
|
|
656
|
+
|
|
657
|
+
Promise.all(pagePromises)
|
|
658
|
+
.then(pages => {
|
|
659
|
+
const pageExport = pages.reduce((dict, combined) => {
|
|
660
|
+
dict[combined.slug] = combined.page
|
|
661
|
+
return dict
|
|
662
|
+
}, {})
|
|
663
|
+
// TODO: this fails for a very large site
|
|
664
|
+
res.json(pageExport)
|
|
665
|
+
})
|
|
666
|
+
.catch(error => {
|
|
667
|
+
res.e(error)
|
|
668
|
+
})
|
|
681
669
|
})
|
|
682
670
|
})
|
|
683
671
|
|
|
@@ -692,32 +680,31 @@ module.exports = exports = argv => {
|
|
|
692
680
|
|
|
693
681
|
app.get('/system/version.json', admin, (req, res) => {
|
|
694
682
|
const versions = {}
|
|
695
|
-
const wikiModule =
|
|
683
|
+
const wikiModule = require.main
|
|
696
684
|
versions[wikiModule.require('./package').name] = wikiModule.require('./package').version
|
|
697
685
|
versions[wikiModule.require('wiki-server/package').name] = wikiModule.require('wiki-server/package').version
|
|
698
686
|
versions[wikiModule.require('wiki-client/package').name] = wikiModule.require('wiki-client/package').version
|
|
687
|
+
|
|
699
688
|
versions['security'] = {}
|
|
700
|
-
|
|
689
|
+
Object.keys(require.main.require('./package').dependencies)
|
|
690
|
+
.filter(depend => depend.startsWith('wiki-security'))
|
|
691
|
+
.forEach(key => {
|
|
692
|
+
versions.security[key] = wikiModule.require(`${key}/package`).version
|
|
693
|
+
})
|
|
701
694
|
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
707
|
-
).version
|
|
708
|
-
} else {
|
|
709
|
-
versions.plugins[wikiModule.require(plugin + '/package').name] = wikiModule.require(
|
|
710
|
-
plugin + '/package',
|
|
711
|
-
).version
|
|
712
|
-
}
|
|
713
|
-
res.json(versions)
|
|
695
|
+
versions['plugins'] = {}
|
|
696
|
+
Object.keys(require.main.require('./package').dependencies)
|
|
697
|
+
.filter(depend => depend.startsWith('wiki-plugin'))
|
|
698
|
+
.forEach(key => {
|
|
699
|
+
versions.plugins[key] = wikiModule.require(`${key}/package`).version
|
|
714
700
|
})
|
|
715
|
-
|
|
701
|
+
|
|
702
|
+
res.json(versions)
|
|
716
703
|
})
|
|
717
704
|
|
|
718
705
|
// ##### Proxy routes #####
|
|
719
706
|
|
|
720
|
-
app.get('/proxy/*', authorized, (req, res) => {
|
|
707
|
+
app.get('/proxy/*splat', authorized, (req, res) => {
|
|
721
708
|
const pathParts = req.originalUrl.split('/')
|
|
722
709
|
const remoteHost = pathParts[2]
|
|
723
710
|
pathParts.splice(0, 3)
|
|
@@ -731,7 +718,7 @@ module.exports = exports = argv => {
|
|
|
731
718
|
requestURL.endsWith('.jpg') ||
|
|
732
719
|
pathParts[0] === 'plugin'
|
|
733
720
|
) {
|
|
734
|
-
fetch(requestURL, {
|
|
721
|
+
fetch(requestURL, { signal: AbortSignal.timeout(2000) })
|
|
735
722
|
.then(async fetchRes => {
|
|
736
723
|
if (fetchRes.ok) {
|
|
737
724
|
res.set('content-type', fetchRes.headers.get('content-type'))
|
package/lib/sitemap.js
CHANGED
|
@@ -119,8 +119,8 @@ module.exports = exports = argv => {
|
|
|
119
119
|
}
|
|
120
120
|
|
|
121
121
|
const sitemapSave = (sitemap, cb) => {
|
|
122
|
-
fs.
|
|
123
|
-
if (
|
|
122
|
+
fs.access(argv.status, fs.constants.F_OK, err => {
|
|
123
|
+
if (!err) {
|
|
124
124
|
writeFileAtomic(sitemapLoc, JSON.stringify(sitemap), e => {
|
|
125
125
|
if (e) return cb(e)
|
|
126
126
|
cb()
|
|
@@ -136,8 +136,8 @@ module.exports = exports = argv => {
|
|
|
136
136
|
}
|
|
137
137
|
|
|
138
138
|
const sitemapRestore = cb => {
|
|
139
|
-
fs.
|
|
140
|
-
if (
|
|
139
|
+
fs.access(sitemapLoc, fs.constants.F_OK, err => {
|
|
140
|
+
if (!err) {
|
|
141
141
|
fs.readFile(sitemapLoc, (err, data) => {
|
|
142
142
|
if (err) return cb(err)
|
|
143
143
|
try {
|
|
@@ -172,8 +172,8 @@ module.exports = exports = argv => {
|
|
|
172
172
|
const xmlmap = { urlset: { $: { xmlns: 'http://www.sitemaps.org/schemas/sitemap/0.9' }, url: xmlmapPages } }
|
|
173
173
|
const builder = new xml2js.Builder()
|
|
174
174
|
const xml = builder.buildObject(xmlmap)
|
|
175
|
-
fs.
|
|
176
|
-
if (
|
|
175
|
+
fs.access(argv.status, fs.constants.F_OK, err => {
|
|
176
|
+
if (!err) {
|
|
177
177
|
writeFileAtomic(xmlSitemapLoc, xml, e => {
|
|
178
178
|
if (e) return cb(e)
|
|
179
179
|
cb()
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wiki-server",
|
|
3
3
|
"description": "A Federated Wiki Server",
|
|
4
|
-
"version": "0.26.0-rc.
|
|
4
|
+
"version": "0.26.0-rc.2",
|
|
5
5
|
"author": {
|
|
6
6
|
"name": "Ward Cunningham",
|
|
7
7
|
"email": "ward@c2.com",
|
|
@@ -30,22 +30,19 @@
|
|
|
30
30
|
"jon r <jon@allmende.io>"
|
|
31
31
|
],
|
|
32
32
|
"dependencies": {
|
|
33
|
-
"async": "^3.2.4",
|
|
34
33
|
"body-parser": "^2.2.0",
|
|
35
34
|
"client-sessions": "^0.8.0",
|
|
36
35
|
"coffeescript": "^2.5.0",
|
|
37
36
|
"cookie-parser": "^1.4.4",
|
|
38
37
|
"dompurify": "^3.1.0",
|
|
39
38
|
"errorhandler": "^1.5.1",
|
|
40
|
-
"express": "^
|
|
39
|
+
"express": "^5.1.0",
|
|
41
40
|
"express-hbs": "^2.5.0",
|
|
42
41
|
"flates": "0.0.5",
|
|
43
|
-
"glob": "^7.2.3",
|
|
44
42
|
"jsdom": "^26.0.0",
|
|
45
43
|
"method-override": "^3.0.0",
|
|
46
44
|
"minisearch": "^7.1.0",
|
|
47
45
|
"morgan": "^1.9.1",
|
|
48
|
-
"node-fetch": "^3.3.2",
|
|
49
46
|
"write-file-atomic": "^6.0.0",
|
|
50
47
|
"xml2js": "^0.6.2"
|
|
51
48
|
},
|