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/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
@@ -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}
@@ -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