wiki-server 0.25.9 → 0.26.0-rc.0
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/AUTHORS.txt +1 -0
- package/index.js +3 -7
- package/lib/defaultargs.js +83 -0
- package/lib/forward.js +10 -10
- package/lib/page.js +448 -0
- package/lib/plugins.js +54 -0
- package/lib/random_id.js +15 -0
- package/lib/search.js +390 -0
- package/lib/security.js +94 -0
- package/lib/server.js +942 -0
- package/lib/sitemap.js +288 -0
- package/package.json +32 -23
- package/.github/workflows/node.js.yml +0 -31
- package/.mailmap +0 -1
- package/Gruntfile.js +0 -55
- package/lib/defaultargs.coffee +0 -77
- package/lib/page.coffee +0 -371
- package/lib/plugins.coffee +0 -48
- package/lib/random_id.coffee +0 -18
- package/lib/search.coffee +0 -304
- package/lib/security.coffee +0 -86
- package/lib/server.coffee +0 -782
- package/lib/sitemap.coffee +0 -246
- package/test/asdf-test-page +0 -8
- package/test/defaultargs.coffee +0 -10
- package/test/mocha.opts +0 -2
- package/test/page.coffee +0 -61
- package/test/random.coffee +0 -8
- package/test/server.coffee +0 -228
- package/test/sitemap.coffee +0 -110
package/lib/page.coffee
DELETED
|
@@ -1,371 +0,0 @@
|
|
|
1
|
-
###
|
|
2
|
-
* Federated Wiki : Node Server
|
|
3
|
-
*
|
|
4
|
-
* Copyright Ward Cunningham and other contributors
|
|
5
|
-
* Licensed under the MIT license.
|
|
6
|
-
* https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt
|
|
7
|
-
###
|
|
8
|
-
# **page.coffee**
|
|
9
|
-
# Module for interacting with pages persisted on the server.
|
|
10
|
-
# Everything is stored using json flat files.
|
|
11
|
-
|
|
12
|
-
#### Requires ####
|
|
13
|
-
fs = require 'fs'
|
|
14
|
-
path = require 'path'
|
|
15
|
-
events = require 'events'
|
|
16
|
-
glob = require 'glob'
|
|
17
|
-
|
|
18
|
-
async = require 'async'
|
|
19
|
-
|
|
20
|
-
random_id = require './random_id'
|
|
21
|
-
synopsis = require 'wiki-client/lib/synopsis'
|
|
22
|
-
|
|
23
|
-
asSlug = (name) ->
|
|
24
|
-
name.replace(/\s/g, '-').replace(/[^A-Za-z0-9-]/g, '').toLowerCase()
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
# Export a function that generates a page handler
|
|
28
|
-
# when called with options object.
|
|
29
|
-
module.exports = exports = (argv) ->
|
|
30
|
-
|
|
31
|
-
wikiName = new URL(argv.url).hostname
|
|
32
|
-
|
|
33
|
-
fs.mkdir argv.db, { recursive: true }, (e) ->
|
|
34
|
-
if e then throw e
|
|
35
|
-
|
|
36
|
-
#### Private utility methods. ####
|
|
37
|
-
load_parse = (loc, cb, annotations={}) ->
|
|
38
|
-
fs.readFile(loc, (err, data) ->
|
|
39
|
-
return cb(err) if err
|
|
40
|
-
try
|
|
41
|
-
page = JSON.parse(data)
|
|
42
|
-
catch e
|
|
43
|
-
errorPage = path.basename(loc)
|
|
44
|
-
errorPagePath = path.dirname(loc)
|
|
45
|
-
recyclePage = path.resolve(errorPagePath, '..', 'recycle', errorPage)
|
|
46
|
-
fs.exists(path.dirname(recyclePage), (exists) ->
|
|
47
|
-
if exists
|
|
48
|
-
fs.rename(loc, recyclePage, (err) ->
|
|
49
|
-
if err
|
|
50
|
-
console.log "ERROR: moving problem page #{loc} to recycler", err
|
|
51
|
-
else
|
|
52
|
-
console.log "ERROR: problem page #{loc} moved to recycler"
|
|
53
|
-
)
|
|
54
|
-
else
|
|
55
|
-
fs.mkdir(path.dirname(recyclePage), { recursive: true }, (err) ->
|
|
56
|
-
if err
|
|
57
|
-
console.log "ERROR: creating recycler", err
|
|
58
|
-
else
|
|
59
|
-
fs.rename(loc, recyclePage, (err) ->
|
|
60
|
-
if err
|
|
61
|
-
console.log "ERROR: moving problem page #{loc} to recycler", err
|
|
62
|
-
else
|
|
63
|
-
console.log "ERROR: problem page #{loc} moved to recycler"
|
|
64
|
-
)
|
|
65
|
-
)
|
|
66
|
-
)
|
|
67
|
-
return cb(null, 'Error Parsing Page', 404)
|
|
68
|
-
for key, val of annotations
|
|
69
|
-
page[key] = val
|
|
70
|
-
cb(null, page)
|
|
71
|
-
)
|
|
72
|
-
|
|
73
|
-
load_parse_copy = (defloc, file, cb) ->
|
|
74
|
-
fs.readFile(defloc, (err, data) ->
|
|
75
|
-
if err then cb(err)
|
|
76
|
-
try
|
|
77
|
-
page = JSON.parse(data)
|
|
78
|
-
catch e
|
|
79
|
-
return cb(e)
|
|
80
|
-
cb(null, page)
|
|
81
|
-
itself.put(file, page, (err) ->
|
|
82
|
-
if err then cb(err)
|
|
83
|
-
)
|
|
84
|
-
)
|
|
85
|
-
|
|
86
|
-
# Reads and writes are async, but serially queued to avoid race conditions.
|
|
87
|
-
queue = []
|
|
88
|
-
|
|
89
|
-
tryDefaults = (file, cb) ->
|
|
90
|
-
lastDefault = (cb) ->
|
|
91
|
-
defloc = path.join(argv.root, 'default-data', 'pages', file)
|
|
92
|
-
fs.exists defloc, (exists) ->
|
|
93
|
-
if exists
|
|
94
|
-
cb(defloc)
|
|
95
|
-
else
|
|
96
|
-
cb(null)
|
|
97
|
-
if argv.defaults
|
|
98
|
-
defloc = path.join(argv.data, '..', argv.defaults, 'pages', file)
|
|
99
|
-
console.log 'firstDefault', defloc
|
|
100
|
-
fs.exists defloc, (exists) ->
|
|
101
|
-
if exists
|
|
102
|
-
cb(defloc)
|
|
103
|
-
else
|
|
104
|
-
lastDefault(cb)
|
|
105
|
-
else
|
|
106
|
-
lastDefault(cb)
|
|
107
|
-
|
|
108
|
-
# Main file io function, when called without page it reads,
|
|
109
|
-
# when called with page it writes.
|
|
110
|
-
fileio = (action, file, page, cb) ->
|
|
111
|
-
if file.startsWith 'recycler/'
|
|
112
|
-
loc = path.join(argv.recycler, file.split('/')[1])
|
|
113
|
-
else
|
|
114
|
-
loc = path.join(argv.db, file)
|
|
115
|
-
switch action
|
|
116
|
-
when 'delete'
|
|
117
|
-
if file.startsWith 'recycler/'
|
|
118
|
-
# delete from recycler
|
|
119
|
-
fs.exists(loc, (exists) ->
|
|
120
|
-
if exists
|
|
121
|
-
fs.unlink(loc, (err) ->
|
|
122
|
-
cb(err)
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
else
|
|
126
|
-
# move page to recycler
|
|
127
|
-
fs.exists(loc, (exists) ->
|
|
128
|
-
if exists
|
|
129
|
-
recycleLoc = path.join(argv.recycler, file)
|
|
130
|
-
fs.exists(path.dirname(recycleLoc), (exists) ->
|
|
131
|
-
if exists
|
|
132
|
-
fs.rename(loc, recycleLoc, (err) ->
|
|
133
|
-
cb(err)
|
|
134
|
-
)
|
|
135
|
-
else
|
|
136
|
-
fs.mkdir(path.dirname(recycleLoc), { recursive: true }, (err) ->
|
|
137
|
-
if err then cb(err)
|
|
138
|
-
fs.rename(loc, recycleLoc, (err) ->
|
|
139
|
-
cb(err)
|
|
140
|
-
)
|
|
141
|
-
)
|
|
142
|
-
)
|
|
143
|
-
else
|
|
144
|
-
cb('page does not exist')
|
|
145
|
-
)
|
|
146
|
-
when 'recycle'
|
|
147
|
-
copyFile = (source, target, cb) ->
|
|
148
|
-
|
|
149
|
-
done = (err) ->
|
|
150
|
-
if !cbCalled
|
|
151
|
-
cb err
|
|
152
|
-
cbCalled = true
|
|
153
|
-
return
|
|
154
|
-
|
|
155
|
-
cbCalled = false
|
|
156
|
-
|
|
157
|
-
rd = fs.createReadStream(source)
|
|
158
|
-
rd.on 'error', (err) ->
|
|
159
|
-
done err
|
|
160
|
-
return
|
|
161
|
-
|
|
162
|
-
wr = fs.createWriteStream(target)
|
|
163
|
-
wr.on 'error', (err) ->
|
|
164
|
-
done err
|
|
165
|
-
return
|
|
166
|
-
wr.on 'close', (ex) ->
|
|
167
|
-
done()
|
|
168
|
-
return
|
|
169
|
-
rd.pipe wr
|
|
170
|
-
return
|
|
171
|
-
|
|
172
|
-
fs.exists(loc, (exists) ->
|
|
173
|
-
if exists
|
|
174
|
-
recycleLoc = path.join(argv.recycler, file)
|
|
175
|
-
fs.exists(path.dirname(recycleLoc), (exists) ->
|
|
176
|
-
if exists
|
|
177
|
-
copyFile(loc, recycleLoc, (err) ->
|
|
178
|
-
cb(err)
|
|
179
|
-
)
|
|
180
|
-
else
|
|
181
|
-
fs.mkdir(path.dirname(recycleLoc), { recursive: true }, (err) ->
|
|
182
|
-
if err then cb(err)
|
|
183
|
-
copyFile(loc, recycleLoc, (err) ->
|
|
184
|
-
cb(err)
|
|
185
|
-
)
|
|
186
|
-
)
|
|
187
|
-
)
|
|
188
|
-
else
|
|
189
|
-
cb('page does not exist')
|
|
190
|
-
)
|
|
191
|
-
when 'get'
|
|
192
|
-
fs.exists(loc, (exists) ->
|
|
193
|
-
if exists
|
|
194
|
-
load_parse(loc, cb, {plugin: undefined})
|
|
195
|
-
else
|
|
196
|
-
tryDefaults(file, (defloc) ->
|
|
197
|
-
if defloc
|
|
198
|
-
load_parse(defloc, cb)
|
|
199
|
-
else
|
|
200
|
-
glob "wiki-plugin-*/pages", {cwd: argv.packageDir}, (e, plugins) ->
|
|
201
|
-
if e then return cb(e)
|
|
202
|
-
|
|
203
|
-
# if no plugins found
|
|
204
|
-
if plugins.length is 0
|
|
205
|
-
cb(null, 'Page not found', 404)
|
|
206
|
-
|
|
207
|
-
giveUp = do ->
|
|
208
|
-
count = plugins.length
|
|
209
|
-
return ->
|
|
210
|
-
count -= 1
|
|
211
|
-
if count is 0
|
|
212
|
-
cb(null, 'Page not found', 404)
|
|
213
|
-
|
|
214
|
-
for plugin in plugins
|
|
215
|
-
do ->
|
|
216
|
-
pluginName = plugin.slice(12, -6)
|
|
217
|
-
pluginloc = path.join(argv.packageDir, plugin, file)
|
|
218
|
-
fs.exists(pluginloc, (exists) ->
|
|
219
|
-
if exists
|
|
220
|
-
load_parse(pluginloc, cb, {plugin: pluginName})
|
|
221
|
-
else
|
|
222
|
-
giveUp()
|
|
223
|
-
)
|
|
224
|
-
)
|
|
225
|
-
)
|
|
226
|
-
when 'put'
|
|
227
|
-
page = JSON.stringify(page, null, 2)
|
|
228
|
-
fs.exists(path.dirname(loc), (exists) ->
|
|
229
|
-
if exists
|
|
230
|
-
fs.writeFile(loc, page, (err) ->
|
|
231
|
-
if err
|
|
232
|
-
console.log "ERROR: write file #{loc} ", err
|
|
233
|
-
cb(err)
|
|
234
|
-
)
|
|
235
|
-
else
|
|
236
|
-
fs.mkdir(path.dirname(loc), { recursive: true }, (err) ->
|
|
237
|
-
if err then cb(err)
|
|
238
|
-
fs.writeFile(loc, page, (err) ->
|
|
239
|
-
if err
|
|
240
|
-
console.log "ERROR: write file #{loc} ", err
|
|
241
|
-
cb(err)
|
|
242
|
-
)
|
|
243
|
-
)
|
|
244
|
-
)
|
|
245
|
-
else
|
|
246
|
-
console.log "pagehandler: unrecognized action #{action}"
|
|
247
|
-
|
|
248
|
-
# Control variable that tells if the serial queue is currently working.
|
|
249
|
-
# Set back to false when all jobs are complete.
|
|
250
|
-
working = false
|
|
251
|
-
|
|
252
|
-
# Keep file io working on queued jobs, but don't block the main thread.
|
|
253
|
-
serial = (item) ->
|
|
254
|
-
if item
|
|
255
|
-
itself.start()
|
|
256
|
-
fileio(item.action, item.file, item.page, (err, data, status) ->
|
|
257
|
-
process.nextTick( ->
|
|
258
|
-
serial(queue.shift())
|
|
259
|
-
)
|
|
260
|
-
item.cb(err, data, status)
|
|
261
|
-
)
|
|
262
|
-
else
|
|
263
|
-
itself.stop()
|
|
264
|
-
|
|
265
|
-
#### Public stuff ####
|
|
266
|
-
# Make the exported object an instance of EventEmitter
|
|
267
|
-
# so other modules can tell if it is working or not.
|
|
268
|
-
itself = new events.EventEmitter
|
|
269
|
-
itself.start = ->
|
|
270
|
-
working = true
|
|
271
|
-
@emit 'working'
|
|
272
|
-
itself.stop = ->
|
|
273
|
-
working = false
|
|
274
|
-
@emit 'finished'
|
|
275
|
-
|
|
276
|
-
itself.isWorking = ->
|
|
277
|
-
working
|
|
278
|
-
|
|
279
|
-
# get method takes a slug and a callback, adding them to the queue,
|
|
280
|
-
# starting serial if it isn't already working.
|
|
281
|
-
itself.get = (file, cb) ->
|
|
282
|
-
queue.push({action: 'get', file, page: null, cb})
|
|
283
|
-
serial(queue.shift()) unless working
|
|
284
|
-
|
|
285
|
-
# put takes a slugged name, the page as a json object, and a callback.
|
|
286
|
-
# adds them to the queue, and starts it unless it is working.
|
|
287
|
-
itself.put = (file, page, cb) ->
|
|
288
|
-
queue.push({action: 'put', file, page, cb})
|
|
289
|
-
serial(queue.shift()) unless working
|
|
290
|
-
|
|
291
|
-
itself.delete = (file, cb) ->
|
|
292
|
-
queue.push({action: 'delete', file, page: null, cb})
|
|
293
|
-
serial(queue.shift()) unless working
|
|
294
|
-
|
|
295
|
-
itself.saveToRecycler = (file, cb) ->
|
|
296
|
-
queue.push({action: 'recycle', file, page: null, cb})
|
|
297
|
-
serial(queue.shift()) unless working
|
|
298
|
-
|
|
299
|
-
editDate = (journal) ->
|
|
300
|
-
for action in (journal || []) by -1
|
|
301
|
-
return action.date if action.date and action.type != 'fork'
|
|
302
|
-
undefined
|
|
303
|
-
|
|
304
|
-
itself.pages = (cb) ->
|
|
305
|
-
|
|
306
|
-
extractPageLinks = (collaborativeLinks, currentItem, currentIndex, array) ->
|
|
307
|
-
# extract collaborative links
|
|
308
|
-
# - this will need extending if we also extract the id of the item containing the link
|
|
309
|
-
try
|
|
310
|
-
linkRe = /\[\[([^\]]+)\]\]/g
|
|
311
|
-
match = undefined
|
|
312
|
-
while (match = linkRe.exec(currentItem.text)) != null
|
|
313
|
-
if not collaborativeLinks.has(asSlug(match[1]))
|
|
314
|
-
collaborativeLinks.set(asSlug(match[1]), currentItem.id)
|
|
315
|
-
if 'reference' == currentItem.type
|
|
316
|
-
if not collaborativeLinks.has(currentItem.slug)
|
|
317
|
-
collaborativeLinks.set(currentItem.slug, currentItem.id)
|
|
318
|
-
catch err
|
|
319
|
-
console.log "METADATA *** #{wikiName} Error extracting links from #{currentIndex} of #{JSON.stringify(array)}", err.message
|
|
320
|
-
collaborativeLinks
|
|
321
|
-
|
|
322
|
-
fs.readdir argv.db, (e, files) ->
|
|
323
|
-
return cb(e) if e
|
|
324
|
-
# used to make sure all of the files are read
|
|
325
|
-
# and processesed in the site map before responding
|
|
326
|
-
doSitemap = (file, cb) ->
|
|
327
|
-
itself.get file, (e, page, status) ->
|
|
328
|
-
return cb() if file.match /^\./
|
|
329
|
-
if e or status is 404
|
|
330
|
-
console.log 'Problem building sitemap:', file, 'e: ', e, 'status:', status
|
|
331
|
-
return cb() # Ignore errors in the pagehandler get.
|
|
332
|
-
|
|
333
|
-
try
|
|
334
|
-
pageLinksMap = page.story.reduce( extractPageLinks, new Map())
|
|
335
|
-
catch err
|
|
336
|
-
console.log "METADATA *** #{wikiName} reduce to extract links on #{file} failed", err.message
|
|
337
|
-
pageLinksMap = []
|
|
338
|
-
#
|
|
339
|
-
if pageLinksMap.size > 0
|
|
340
|
-
pageLinks = Object.fromEntries(pageLinksMap)
|
|
341
|
-
else
|
|
342
|
-
pageLinks = undefined
|
|
343
|
-
|
|
344
|
-
cb null, {
|
|
345
|
-
slug : file
|
|
346
|
-
title : page.title
|
|
347
|
-
date : editDate(page.journal)
|
|
348
|
-
synopsis : synopsis(page)
|
|
349
|
-
links : pageLinks
|
|
350
|
-
}
|
|
351
|
-
|
|
352
|
-
async.map files, doSitemap, (e, sitemap) ->
|
|
353
|
-
return cb(e) if e
|
|
354
|
-
cb null, sitemap.filter (item) -> if item? then true
|
|
355
|
-
|
|
356
|
-
itself.slugs = (cb) ->
|
|
357
|
-
fs.readdir argv.db, {withFileTypes: true}, (e, files) ->
|
|
358
|
-
if e
|
|
359
|
-
console.log 'Problem reading pages directory', e
|
|
360
|
-
cb(e)
|
|
361
|
-
else
|
|
362
|
-
onlyFiles = files.map((i) ->
|
|
363
|
-
if i.isFile()
|
|
364
|
-
return i.name
|
|
365
|
-
else
|
|
366
|
-
return null
|
|
367
|
-
).filter((i) ->
|
|
368
|
-
i != null && !i?.startsWith('.'))
|
|
369
|
-
cb(null, onlyFiles)
|
|
370
|
-
|
|
371
|
-
itself
|
package/lib/plugins.coffee
DELETED
|
@@ -1,48 +0,0 @@
|
|
|
1
|
-
###
|
|
2
|
-
* Federated Wiki : Node Server
|
|
3
|
-
*
|
|
4
|
-
* Copyright Ward Cunningham and other contributors
|
|
5
|
-
* Licensed under the MIT license.
|
|
6
|
-
* https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt
|
|
7
|
-
###
|
|
8
|
-
|
|
9
|
-
# support server-side plugins
|
|
10
|
-
|
|
11
|
-
fs = require 'fs'
|
|
12
|
-
path = require 'path'
|
|
13
|
-
glob = require 'glob'
|
|
14
|
-
events = require 'events'
|
|
15
|
-
{ pathToFileURL } = require 'node:url'
|
|
16
|
-
# forward = require './forward'
|
|
17
|
-
|
|
18
|
-
module.exports = exports = (argv) ->
|
|
19
|
-
|
|
20
|
-
# NOTE: plugins are now in their own package directories alongside this one...
|
|
21
|
-
# Plugins are in directories of the form wiki-package-*
|
|
22
|
-
# those with a server component will have a server directory
|
|
23
|
-
|
|
24
|
-
plugins = {}
|
|
25
|
-
|
|
26
|
-
# http://stackoverflow.com/questions/10914751/loading-node-js-modules-dynamically-based-on-route
|
|
27
|
-
|
|
28
|
-
startServer = (params, plugin) ->
|
|
29
|
-
server = "#{argv.packageDir}/#{plugin}/server/server.js"
|
|
30
|
-
fs.exists server, (exists) ->
|
|
31
|
-
if exists
|
|
32
|
-
console.log 'starting plugin', plugin
|
|
33
|
-
import(pathToFileURL(server)).then((exported) ->
|
|
34
|
-
plugins[plugin] = exported
|
|
35
|
-
plugins[plugin].startServer?(params)
|
|
36
|
-
).catch((e) ->
|
|
37
|
-
console.log 'failed to start plugin', plugin, e?.stack or e
|
|
38
|
-
)
|
|
39
|
-
|
|
40
|
-
startServers = (params) ->
|
|
41
|
-
# emitter = new events.EventEmitter()
|
|
42
|
-
# forward.init params.app, emitter
|
|
43
|
-
# params.emitter = emitter
|
|
44
|
-
glob "wiki-plugin-*", {cwd: argv.packageDir}, (e, plugins) ->
|
|
45
|
-
startServer params, plugin for plugin in plugins
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
{startServers}
|
package/lib/random_id.coffee
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
###
|
|
2
|
-
* Federated Wiki : Node Server
|
|
3
|
-
*
|
|
4
|
-
* Copyright Ward Cunningham and other contributors
|
|
5
|
-
* Licensed under the MIT license.
|
|
6
|
-
* https://github.com/fedwiki/wiki-server/blob/master/LICENSE.txt
|
|
7
|
-
###
|
|
8
|
-
|
|
9
|
-
# **random_id.coffee**
|
|
10
|
-
# Simple random hex generator, takes an optional number of
|
|
11
|
-
# chars that defaults to 16 and returns a random id.
|
|
12
|
-
|
|
13
|
-
random_id = (chars = 16) ->
|
|
14
|
-
[0...chars].map( ->
|
|
15
|
-
Math.floor(Math.random() * 16).toString(16)
|
|
16
|
-
).join('')
|
|
17
|
-
|
|
18
|
-
module.exports = random_id.random_id = random_id
|