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/search.coffee
DELETED
|
@@ -1,304 +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
|
-
# **search.coffee**
|
|
10
|
-
|
|
11
|
-
fs = require 'fs'
|
|
12
|
-
path = require 'path'
|
|
13
|
-
events = require 'events'
|
|
14
|
-
url = require 'node:url'
|
|
15
|
-
writeFileAtomic = require 'write-file-atomic'
|
|
16
|
-
|
|
17
|
-
miniSearch = require 'minisearch'
|
|
18
|
-
|
|
19
|
-
module.exports = exports = (argv) ->
|
|
20
|
-
|
|
21
|
-
wikiName = new URL(argv.url).hostname
|
|
22
|
-
|
|
23
|
-
siteIndex = []
|
|
24
|
-
|
|
25
|
-
queue = []
|
|
26
|
-
|
|
27
|
-
searchPageHandler = null
|
|
28
|
-
|
|
29
|
-
# ms since last update we will remove index from memory
|
|
30
|
-
# orig - searchTimeoutMs = 1200000
|
|
31
|
-
searchTimeoutMs = 120000 # temp reduce to 2 minutes
|
|
32
|
-
searchTimeoutHandler = null
|
|
33
|
-
|
|
34
|
-
siteIndexLoc = path.join(argv.status, 'site-index.json')
|
|
35
|
-
indexUpdateFlag = path.join(argv.status, 'index-updated')
|
|
36
|
-
|
|
37
|
-
working = false
|
|
38
|
-
|
|
39
|
-
touch = (file, cb) ->
|
|
40
|
-
fs.stat file, (err, stats) ->
|
|
41
|
-
return cb() if err is null
|
|
42
|
-
fs.open file, 'w', (err,fd) ->
|
|
43
|
-
cb(err) if err
|
|
44
|
-
fs.close fd, (err) ->
|
|
45
|
-
cb(err)
|
|
46
|
-
|
|
47
|
-
searchPageUpdate = (slug, page, cb) ->
|
|
48
|
-
# to update we have to remove the page first, and then readd it
|
|
49
|
-
try
|
|
50
|
-
pageText = page.story.reduce( extractPageText, '')
|
|
51
|
-
catch err
|
|
52
|
-
console.log "SITE INDEX *** #{wikiName} reduce to extract the text on #{slug} failed", err.message
|
|
53
|
-
pageText = ""
|
|
54
|
-
if siteIndex.has slug
|
|
55
|
-
siteIndex.replace {
|
|
56
|
-
'id': slug
|
|
57
|
-
'title': page.title
|
|
58
|
-
'content': pageText
|
|
59
|
-
}
|
|
60
|
-
else
|
|
61
|
-
siteIndex.add {
|
|
62
|
-
'id': slug
|
|
63
|
-
'title': page.title
|
|
64
|
-
'content': pageText
|
|
65
|
-
}
|
|
66
|
-
cb()
|
|
67
|
-
|
|
68
|
-
searchPageRemove = (slug, cb) ->
|
|
69
|
-
# remove page from index
|
|
70
|
-
timeLabel = "SITE INDEX page remove #{slug} - #{wikiName}"
|
|
71
|
-
try
|
|
72
|
-
siteIndex.discard slug
|
|
73
|
-
catch err
|
|
74
|
-
# swallow error, if the page was not in index
|
|
75
|
-
console.log "removing #{slug} from index #{wikiName} failed", err unless err.message.includes('not in the index')
|
|
76
|
-
cb()
|
|
77
|
-
|
|
78
|
-
searchSave = (siteIndex, cb) ->
|
|
79
|
-
# save index to file
|
|
80
|
-
fs.exists argv.status, (exists) ->
|
|
81
|
-
if exists
|
|
82
|
-
writeFileAtomic siteIndexLoc, JSON.stringify(siteIndex), (e) ->
|
|
83
|
-
return cb(e) if e
|
|
84
|
-
touch indexUpdateFlag, (err) ->
|
|
85
|
-
cb()
|
|
86
|
-
else
|
|
87
|
-
fs.mkdir argv.status, { recursive: true }, ->
|
|
88
|
-
writeFileAtomic siteIndexLoc, JSON.stringify(siteIndex), (e) ->
|
|
89
|
-
return cb(e) if e
|
|
90
|
-
touch indexUpdateFlag, (err) ->
|
|
91
|
-
cb()
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
searchRestore = (cb) ->
|
|
95
|
-
# restore index, or create if it doesn't already exist
|
|
96
|
-
fs.exists siteIndexLoc, (exists) ->
|
|
97
|
-
if exists
|
|
98
|
-
fs.readFile(siteIndexLoc, (err, data) ->
|
|
99
|
-
return cb(err) if err
|
|
100
|
-
try
|
|
101
|
-
siteIndex = miniSearch.loadJSON data,
|
|
102
|
-
fields: ['title', 'content']
|
|
103
|
-
catch e
|
|
104
|
-
return cb(e)
|
|
105
|
-
process.nextTick( ->
|
|
106
|
-
serial(queue.shift())))
|
|
107
|
-
|
|
108
|
-
serial = (item) ->
|
|
109
|
-
if item
|
|
110
|
-
switch item.action
|
|
111
|
-
when "update"
|
|
112
|
-
itself.start()
|
|
113
|
-
searchPageUpdate(item.slug, item.page, (e) ->
|
|
114
|
-
process.nextTick( ->
|
|
115
|
-
serial(queue.shift())
|
|
116
|
-
)
|
|
117
|
-
)
|
|
118
|
-
when "remove"
|
|
119
|
-
itself.start()
|
|
120
|
-
searchPageRemove(item.slug, (e) ->
|
|
121
|
-
process.nextTick( ->
|
|
122
|
-
serial(queue.shift())
|
|
123
|
-
)
|
|
124
|
-
)
|
|
125
|
-
else
|
|
126
|
-
console.log "SITE INDEX *** unexpected action #{item.action} for #{item.page}"
|
|
127
|
-
process.nextTick( ->
|
|
128
|
-
serial(queue.shift))
|
|
129
|
-
else
|
|
130
|
-
searchSave siteIndex, (e) ->
|
|
131
|
-
console.log "SITE INDEX *** save failed: " + e if e
|
|
132
|
-
itself.stop()
|
|
133
|
-
|
|
134
|
-
extractItemText = (text) ->
|
|
135
|
-
return text.replace(/\[([^\]]*?)\][\[\(].*?[\]\)]/g, " $1 ")
|
|
136
|
-
.replace(/\[{2}|\[(?:[\S]+)|\]{1,2}/g, ' ')
|
|
137
|
-
.replace(/\n/g, ' ')
|
|
138
|
-
.replace(/<style.*?<\/style>/g, ' ')
|
|
139
|
-
.replace(/<(?:"[^"]*"['"]*|'[^']*'['"]*|[^'">])+>/g, ' ')
|
|
140
|
-
.replace(/<(?:[^>])+>/g, ' ')
|
|
141
|
-
.replace(/(https?.*?)(?=\p{White_Space}|\p{Quotation_Mark}|$)/gu, (match) ->
|
|
142
|
-
myUrl = url.parse(match)
|
|
143
|
-
return myUrl.hostname + ' ' + myUrl.pathname)
|
|
144
|
-
.replace(/[\p{P}\p{Emoji}\p{Symbol}}]+/gu, ' ')
|
|
145
|
-
.replace /[\p{White_Space}\n\t]+/gu, ' '
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
extractPageText = (pageText, currentItem, currentIndex, array) ->
|
|
149
|
-
# console.log('extractPageText', pageText, currentItem, currentIndex, array)
|
|
150
|
-
try
|
|
151
|
-
if currentItem.text?
|
|
152
|
-
switch currentItem.type
|
|
153
|
-
when 'paragraph', 'markdown', 'html', 'reference', 'image', 'pagefold', 'math', 'mathjax', 'code'
|
|
154
|
-
pageText += ' ' + extractItemText currentItem.text
|
|
155
|
-
when 'audio', 'video', 'frame'
|
|
156
|
-
pageText += ' ' + extractItemText(currentItem.text.split(/\r\n?|\n/)
|
|
157
|
-
.map((line) ->
|
|
158
|
-
firstWord = line.split(/\p{White_Space}/u)[0]
|
|
159
|
-
if firstWord.startsWith('http') or firstWord.toUpperCase() is firstWord or firstWord.startsWith('//')
|
|
160
|
-
# line is markup
|
|
161
|
-
return ''
|
|
162
|
-
else
|
|
163
|
-
return line
|
|
164
|
-
).join(' '))
|
|
165
|
-
catch err
|
|
166
|
-
throw new Error("Error extracting text from #{currentIndex}, #{JSON.stringify(currentItem)} #{err}, #{err.stack}")
|
|
167
|
-
pageText
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
#### Public stuff ####
|
|
171
|
-
|
|
172
|
-
itself = new events.EventEmitter
|
|
173
|
-
itself.start = ->
|
|
174
|
-
clearTimeout(searchTimeoutHandler)
|
|
175
|
-
working = true
|
|
176
|
-
@emit 'indexing'
|
|
177
|
-
itself.stop = ->
|
|
178
|
-
clearsearch = ->
|
|
179
|
-
console.log "SITE INDEX #{wikiName} : removed from memory"
|
|
180
|
-
siteIndex = []
|
|
181
|
-
clearTimeout(searchTimeoutHandler)
|
|
182
|
-
searchTimeoutHandler = setTimeout clearsearch, searchTimeoutMs
|
|
183
|
-
working = false
|
|
184
|
-
@emit 'indexed'
|
|
185
|
-
|
|
186
|
-
itself.isWorking = ->
|
|
187
|
-
working
|
|
188
|
-
|
|
189
|
-
itself.createIndex = (pagehandler) ->
|
|
190
|
-
|
|
191
|
-
itself.start()
|
|
192
|
-
|
|
193
|
-
# we save the pagehandler, so we can recreate the site index if it is removed
|
|
194
|
-
searchPageHandler = pagehandler if !searchPageHandler?
|
|
195
|
-
|
|
196
|
-
#timeLabel = "SITE INDEX #{wikiName} : Created"
|
|
197
|
-
#console.time timeLabel
|
|
198
|
-
|
|
199
|
-
pagehandler.slugs (e, slugs) ->
|
|
200
|
-
if e
|
|
201
|
-
console.log "SITE INDEX *** createIndex #{wikiName} error:", e
|
|
202
|
-
itself.stop()
|
|
203
|
-
return e
|
|
204
|
-
|
|
205
|
-
siteIndex = new miniSearch({
|
|
206
|
-
fields: ['title', 'content']
|
|
207
|
-
})
|
|
208
|
-
|
|
209
|
-
indexPromises = slugs.map (slug) ->
|
|
210
|
-
return new Promise (resolve) ->
|
|
211
|
-
pagehandler.get slug, (err, page) ->
|
|
212
|
-
if err
|
|
213
|
-
console.log "SITE INDEX *** #{wikiName}: error reading page", slug
|
|
214
|
-
return
|
|
215
|
-
# page
|
|
216
|
-
try
|
|
217
|
-
pageText = page.story.reduce( extractPageText, '')
|
|
218
|
-
catch err
|
|
219
|
-
console.log "SITE INDEX *** #{wikiName} reduce to extract text on #{slug} failed", err.message
|
|
220
|
-
# console.log "page", page
|
|
221
|
-
pageText = ""
|
|
222
|
-
siteIndex.add {
|
|
223
|
-
'id': slug
|
|
224
|
-
'title': page.title
|
|
225
|
-
'content': pageText
|
|
226
|
-
}
|
|
227
|
-
resolve()
|
|
228
|
-
|
|
229
|
-
Promise.all(indexPromises)
|
|
230
|
-
.then () ->
|
|
231
|
-
# console.timeEnd timeLabel
|
|
232
|
-
process.nextTick ( ->
|
|
233
|
-
serial(queue.shift()))
|
|
234
|
-
|
|
235
|
-
itself.removePage = (slug) ->
|
|
236
|
-
action = "remove"
|
|
237
|
-
queue.push({action, slug })
|
|
238
|
-
if Array.isArray(siteIndex) and !working
|
|
239
|
-
itself.start()
|
|
240
|
-
searchRestore (e) ->
|
|
241
|
-
console.log "SITE INDEX *** Problems restoring search index #{wikiName}:" + e if e
|
|
242
|
-
itself.createIndex(searchPageHandler)
|
|
243
|
-
else
|
|
244
|
-
serial(queue.shift()) unless working
|
|
245
|
-
|
|
246
|
-
itself.update = (slug, page) ->
|
|
247
|
-
action = "update"
|
|
248
|
-
queue.push({action, slug, page})
|
|
249
|
-
if Array.isArray(siteIndex) and !working
|
|
250
|
-
itself.start()
|
|
251
|
-
searchRestore( (e) ->
|
|
252
|
-
console.log "SITE INDEX *** Problems restoring search index #{wikiName}:" + e if e
|
|
253
|
-
itself.createIndex(searchPageHandler))
|
|
254
|
-
else
|
|
255
|
-
serial(queue.shift()) unless working
|
|
256
|
-
|
|
257
|
-
itself.startUp = (pagehandler) ->
|
|
258
|
-
# called on server startup, here we check if wiki already is index
|
|
259
|
-
# we only create an index if there is either no index or there have been updates since last startup
|
|
260
|
-
console.log "SITE INDEX #{wikiName} : StartUp"
|
|
261
|
-
fs.stat siteIndexLoc, (err, stats) ->
|
|
262
|
-
if err is null
|
|
263
|
-
# site index exists, but has it been updated?
|
|
264
|
-
fs.stat indexUpdateFlag, (err, stats) ->
|
|
265
|
-
if !err
|
|
266
|
-
# index has been updated, so recreate it.
|
|
267
|
-
itself.createIndex pagehandler
|
|
268
|
-
# remove the update flag once the index has been created
|
|
269
|
-
itself.once 'indexed', ->
|
|
270
|
-
fs.unlink indexUpdateFlag, (err) ->
|
|
271
|
-
console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err
|
|
272
|
-
else
|
|
273
|
-
# not been updated, but is it the correct version?
|
|
274
|
-
fs.readFile siteIndexLoc, (err, data) ->
|
|
275
|
-
if !err
|
|
276
|
-
try
|
|
277
|
-
testIndex = JSON.parse(data)
|
|
278
|
-
catch err
|
|
279
|
-
testIndex = {}
|
|
280
|
-
if testIndex.serializationVersion != 2
|
|
281
|
-
console.log "+++ SITE INDEX #{wikiName} : updating to latest version."
|
|
282
|
-
itself.createIndex pagehandler
|
|
283
|
-
# remove the update flag once the index has been created
|
|
284
|
-
itself.once 'indexed', ->
|
|
285
|
-
fs.unlink indexUpdateFlag, (err) ->
|
|
286
|
-
console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err
|
|
287
|
-
else
|
|
288
|
-
console.log "+++ SITE INDEX #{wikiName} : error reading index - attempting creating"
|
|
289
|
-
itself.createIndex pagehandler
|
|
290
|
-
# remove the update flag once the index has been created
|
|
291
|
-
itself.once 'indexed', ->
|
|
292
|
-
fs.unlink indexUpdateFlag, (err) ->
|
|
293
|
-
console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err
|
|
294
|
-
else
|
|
295
|
-
# index does not exist, so create it
|
|
296
|
-
itself.createIndex pagehandler
|
|
297
|
-
# remove the update flag once the index has been created
|
|
298
|
-
itself.once 'indexed', ->
|
|
299
|
-
fs.unlink indexUpdateFlag, (err) ->
|
|
300
|
-
console.log "+++ SITE INDEX #{wikiName} : unable to delete update flag" if err
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
itself
|
package/lib/security.coffee
DELETED
|
@@ -1,86 +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-node-server/blob/master/LICENSE.txt
|
|
7
|
-
###
|
|
8
|
-
# **security.coffee**
|
|
9
|
-
# Module for default site security.
|
|
10
|
-
#
|
|
11
|
-
# This module is not intented for use, but is here to catch a problem with
|
|
12
|
-
# configuration of security. It does not provide any authentication, but will
|
|
13
|
-
# allow the server to run read-only.
|
|
14
|
-
|
|
15
|
-
#### Requires ####
|
|
16
|
-
fs = require 'fs'
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
# Export a function that generates security handler
|
|
20
|
-
# when called with options object.
|
|
21
|
-
module.exports = exports = (log, loga, argv) ->
|
|
22
|
-
security={}
|
|
23
|
-
|
|
24
|
-
#### Private utility methods. ####
|
|
25
|
-
|
|
26
|
-
user = ''
|
|
27
|
-
|
|
28
|
-
owner = ''
|
|
29
|
-
|
|
30
|
-
admin = argv.admin
|
|
31
|
-
|
|
32
|
-
# save the location of the identity file
|
|
33
|
-
idFile = argv.id
|
|
34
|
-
|
|
35
|
-
#### Public stuff ####
|
|
36
|
-
|
|
37
|
-
security.authenticate_session = ->
|
|
38
|
-
(req, res, next) ->
|
|
39
|
-
# not possible to login, so always false
|
|
40
|
-
req.isAuthenticated = ->
|
|
41
|
-
return false
|
|
42
|
-
next()
|
|
43
|
-
|
|
44
|
-
# Retrieve owner infomation from identity file in status directory
|
|
45
|
-
security.retrieveOwner = (cb) ->
|
|
46
|
-
fs.exists idFile, (exists) ->
|
|
47
|
-
if exists
|
|
48
|
-
fs.readFile(idFile, (err, data) ->
|
|
49
|
-
if err then return cb err
|
|
50
|
-
owner += data
|
|
51
|
-
cb())
|
|
52
|
-
else
|
|
53
|
-
owner = ''
|
|
54
|
-
cb()
|
|
55
|
-
|
|
56
|
-
# Return the owners name
|
|
57
|
-
security.getOwner = ->
|
|
58
|
-
if !owner.name?
|
|
59
|
-
ownerName = ''
|
|
60
|
-
else
|
|
61
|
-
ownerName = owner.name
|
|
62
|
-
ownerName
|
|
63
|
-
|
|
64
|
-
security.getUser = (req) ->
|
|
65
|
-
return ''
|
|
66
|
-
|
|
67
|
-
security.isAuthorized = (req) ->
|
|
68
|
-
# nobody is authorized - everything is read-only
|
|
69
|
-
# unless legacy support, when unclaimed sites can be editted.
|
|
70
|
-
if owner == ''
|
|
71
|
-
if argv.security_legacy
|
|
72
|
-
return true
|
|
73
|
-
else
|
|
74
|
-
return false
|
|
75
|
-
else
|
|
76
|
-
return false
|
|
77
|
-
|
|
78
|
-
# Wiki server admin
|
|
79
|
-
security.isAdmin = ->
|
|
80
|
-
return false
|
|
81
|
-
|
|
82
|
-
security.defineRoutes = (app, cors, updateOwner) ->
|
|
83
|
-
# default security does not have any routes
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
security
|