|
1 | | -# plugmatic plugin, server-side component |
2 | | -# These handlers are launched with the wiki server. |
3 | | - |
4 | | -fs = require 'fs' |
5 | | -glob = require 'glob' |
6 | | -async = require 'async' |
7 | | -jsonfile = require 'jsonfile' |
8 | | -https = require 'https' |
9 | | -execFile = require('child_process').execFile |
10 | | - |
11 | | - |
12 | | -github = (path, done) -> |
13 | | - options = |
14 | | - host: 'raw.githubusercontent.com' |
15 | | - port: 443 |
16 | | - method: 'GET' |
17 | | - path: path |
18 | | - try |
19 | | - req = https.get options, (res) -> |
20 | | - res.setEncoding 'utf8' |
21 | | - data = '' |
22 | | - res.on 'error', () -> |
23 | | - done null |
24 | | - res.on 'timeout', () -> |
25 | | - done null |
26 | | - res.on 'data', (d) -> |
27 | | - data += d |
28 | | - res.on 'end', () -> |
29 | | - done data |
30 | | - req.on 'error', () -> |
31 | | - done null |
32 | | - catch e |
33 | | - done null |
34 | | - |
35 | | -# http://www.sebastianseilund.com/nodejs-async-in-practice |
36 | | - |
37 | | -startServer = (params) -> |
38 | | - app = params.app |
39 | | - argv = params.argv |
40 | | - bundle = null |
41 | | - |
42 | | - github '/fedwiki/wiki/master/package.json', (data) -> |
43 | | - bundle = |
44 | | - date: Date.now() |
45 | | - data: JSON.parse data||'{"dependencies":{}}' |
46 | | - |
47 | | - route = (endpoint) -> "/plugin/plugmatic/#{endpoint}" |
48 | | - path = (file) -> "#{argv.packageDir}/#{file}" |
49 | | - |
50 | | - |
51 | | - info = (file, done) -> |
52 | | - plugin = file.slice 12 |
53 | | - site = {plugin} |
54 | | - |
55 | | - birth = (cb) -> |
56 | | - fs.stat path("#{file}/client/#{plugin}.js"), (err, stat) -> |
57 | | - site.birth = stat?.birthtime?.getTime(); cb() |
58 | | - pages = (cb) -> |
59 | | - synopsis = (slug, cb2) -> |
60 | | - jsonfile.readFile path("#{file}/pages/#{slug}"), {throws:false}, (err, page) -> |
61 | | - title = page.title || slug |
62 | | - synopsis = page.story?[0]?.text || page.story?[1]?.text || 'empty' |
63 | | - cb2 null, {file, slug, title, synopsis} |
64 | | - fs.readdir path("#{file}/pages"), (err, slugs) -> |
65 | | - async.map slugs||[], synopsis, (err, pages) -> |
66 | | - site.pages = pages; cb() |
67 | | - packagejson = (cb) -> |
68 | | - jsonfile.readFile path("#{file}/package.json"), {throws:false}, (err, packagejson) -> |
69 | | - site.package = packagejson; cb() |
70 | | - factory = (cb) -> |
71 | | - jsonfile.readFile path("#{file}/factory.json"), {throws:false}, (err, factory) -> |
72 | | - site.factory = factory; cb() |
73 | | - authors = (cb) -> |
74 | | - fs.readFile path("#{file}/AUTHORS.txt"), 'utf-8', (err, authors) -> |
75 | | - site.authors = authors; cb() |
76 | | - # persona = (cb) -> |
77 | | - # fs.readFile path("#{file}/status/persona.identity"),'utf8', (err, identity) -> |
78 | | - # site.persona = identity; cb() |
79 | | - # openid = (cb) -> |
80 | | - # fs.readFile path("#{file}/status/open_id.identity"),'utf8', (err, identity) -> |
81 | | - # site.openid = identity; cb() |
82 | | - async.series [birth,authors,packagejson,factory,pages], (err) -> |
83 | | - done null, site |
84 | | - |
85 | | - plugmap = (done) -> |
86 | | - glob "wiki-plugin-*", {cwd: argv.packageDir}, (err, files) -> |
87 | | - return done(err,null) if err |
88 | | - async.map files||[], info, (err, install) -> |
89 | | - return done(err,null) if err |
90 | | - done(null, install) |
91 | | - |
92 | | - |
93 | | - view = (plugin, done) -> |
94 | | - if /^\w+$/.test(plugin) |
95 | | - pkg = "wiki-plugin-#{plugin}" |
96 | | - execFile 'npm', ['view', "#{pkg}", '--json'], (err, stdout, stderr) -> |
97 | | - try npm = JSON.parse stdout |
98 | | - done null, {plugin, pkg, npm} |
99 | | - |
100 | | - farm = (req, res, next) -> |
101 | | - if argv.f |
102 | | - next() |
103 | | - else |
104 | | - res.status(404).send {error: 'service requires farm mode'} |
105 | | - |
106 | | - admin = (req, res, next) -> |
107 | | - if app.securityhandler.isAdmin(req) |
108 | | - next() |
109 | | - else |
110 | | - admin = "none specified" unless argv.admin |
111 | | - user = "not logged in" unless req.session?.passport?.user || req.session?.email || req.session?.friend |
112 | | - res.status(403).send {error: 'service requires admin user', admin, user} |
113 | | - |
114 | | - app.get route('page/:slug.json'), (req, res) -> |
115 | | - plugmap (err, install) -> |
116 | | - for i in install |
117 | | - for p in i.pages |
118 | | - if p.slug is req.params.slug |
119 | | - return jsonfile.readFile path("#{p.file}/pages/#{p.slug}"), {throws:false}, (err, page) -> |
120 | | - res.json page |
121 | | - res.sendStatus 404 |
122 | | - |
123 | | - app.get route('file/:file/slug/:slug'), (req, res) -> |
124 | | - jsonfile.readFile path("#{req.params.file}/pages/#{req.params.slug}"), {throws:false}, (err, page) -> |
125 | | - if err |
126 | | - res.sendStatus 404 |
127 | | - else |
128 | | - res.json page |
129 | | - |
130 | | - app.get route('sitemap.json'), (req, res) -> |
131 | | - plugmap (err, install) -> |
132 | | - # http://stackoverflow.com/a/4631593 |
133 | | - res.json [].concat (i.pages for i in install)... |
134 | | - |
135 | | - app.get route('plugins'), (req, res) -> |
136 | | - glob "wiki-plugin-*", {cwd: argv.packageDir}, (err, files) -> |
137 | | - return res.e err if err |
138 | | - async.map files||[], info, (err, install) -> |
139 | | - return res.e err if err |
140 | | - res.json {install, bundle} |
141 | | - |
142 | | - app.post route('plugins'), (req, res) -> |
143 | | - payload = {bundle} |
144 | | - |
145 | | - installed = (cb) -> |
146 | | - files = ("wiki-plugin-#{plugin}" for plugin in req.body.plugins||[]) |
147 | | - async.map files||[], info, (err, install) -> |
148 | | - payload.install = install; cb() |
149 | | - |
150 | | - published = (cb) -> |
151 | | - async.map req.body.plugins||[], view, (err, results) -> |
152 | | - payload.publish = results; cb() |
153 | | - |
154 | | - async.parallel [installed, published], (err) -> |
155 | | - res.json payload |
156 | | - |
157 | | - app.get route('view/:pkg'), (req, res) -> |
158 | | - if /^\w+$/.test(req.params.pkg) |
159 | | - pkg = "wiki-plugin-#{req.params.pkg}" |
160 | | - res.setHeader 'Content-Type', 'application/json' |
161 | | - execFile('npm', ['view', "#{pkg}", '--json']).stdout.pipe(res) |
162 | | - |
163 | | - app.post route('install'), admin, (req, res) -> |
164 | | - if /^\w+$/.test(req.body.plugin) and /^[\w.-]+$/.test(req.body.version) |
165 | | - pkg = "wiki-plugin-#{req.body.plugin}@#{req.body.version}" |
166 | | - console.log "plugmatic installing #{pkg}" |
167 | | - execFile 'npm', ['install', "#{pkg}", '--json'], {cwd: argv.packageDir+'/..'}, (err, stdout, stderr) -> |
168 | | - try npm = JSON.parse stdout |
169 | | - if err |
170 | | - res.status(400).json {error: 'server unable to install plugin', npm, stderr} |
171 | | - else |
172 | | - info "wiki-plugin-#{req.body.plugin}", (err, row) -> |
173 | | - res.json {installed: req.body.version, npm, stderr, row} |
174 | | - |
175 | | - app.post route('restart'), admin, (req, res) -> |
176 | | - console.log 'plugmatic exit to restart' |
177 | | - res.sendStatus 200 |
178 | | - process.exit 0 |
179 | | - |
180 | | - |
181 | | -module.exports = {startServer} |
| 1 | +/* |
| 2 | + * decaffeinate suggestions: |
| 3 | + * DS101: Remove unnecessary use of Array.from |
| 4 | + * DS102: Remove unnecessary code created because of implicit returns |
| 5 | + * Full docs: https://github.com/decaffeinate/decaffeinate/blob/main/docs/suggestions.md |
| 6 | + */ |
| 7 | +// plugmatic plugin, server-side component |
| 8 | +// These handlers are launched with the wiki server. |
| 9 | + |
| 10 | +const fs = require('fs'); |
| 11 | +const glob = require('glob'); |
| 12 | +const asyncLib = require('async'); |
| 13 | +const jsonfile = require('jsonfile'); |
| 14 | +const https = require('https'); |
| 15 | +const { |
| 16 | + execFile |
| 17 | +} = require('child_process'); |
| 18 | + |
| 19 | + |
| 20 | +const github = function(path, done) { |
| 21 | + const options = { |
| 22 | + host: 'raw.githubusercontent.com', |
| 23 | + port: 443, |
| 24 | + method: 'GET', |
| 25 | + path |
| 26 | + }; |
| 27 | + try { |
| 28 | + const req = https.get(options, function(res) { |
| 29 | + res.setEncoding('utf8'); |
| 30 | + let data = ''; |
| 31 | + res.on('error', () => done(null)); |
| 32 | + res.on('timeout', () => done(null)); |
| 33 | + res.on('data', d => data += d); |
| 34 | + return res.on('end', () => done(data)); |
| 35 | + }); |
| 36 | + return req.on('error', () => done(null)); |
| 37 | + } catch (e) { |
| 38 | + return done(null); |
| 39 | + } |
| 40 | +}; |
| 41 | + |
| 42 | +// http://www.sebastianseilund.com/nodejs-async-in-practice |
| 43 | + |
| 44 | +const startServer = function(params) { |
| 45 | + const { |
| 46 | + app |
| 47 | + } = params; |
| 48 | + const { |
| 49 | + argv |
| 50 | + } = params; |
| 51 | + let bundle = null; |
| 52 | + |
| 53 | + github('/fedwiki/wiki/master/package.json', data => bundle = { |
| 54 | + date: Date.now(), |
| 55 | + data: JSON.parse(data||'{"dependencies":{}}') |
| 56 | + }); |
| 57 | + |
| 58 | + const route = endpoint => `/plugin/plugmatic/${endpoint}`; |
| 59 | + const path = file => `${argv.packageDir}/${file}`; |
| 60 | + |
| 61 | + |
| 62 | + const info = function(file, done) { |
| 63 | + const plugin = file.slice(12); |
| 64 | + const site = {plugin}; |
| 65 | + |
| 66 | + const birth = cb => fs.stat(path(`${file}/client/${plugin}.js`), function(err, stat) { |
| 67 | + site.birth = stat?.birthtime?.getTime(); return cb(); |
| 68 | + }); |
| 69 | + const pages = function(cb) { |
| 70 | + var synopsis = (slug, cb2) => jsonfile.readFile(path(`${file}/pages/${slug}`), {throws:false}, function(err, page) { |
| 71 | + const title = page.title || slug; |
| 72 | + synopsis = page.story?.[0]?.text || page.story?.[1]?.text || 'empty'; |
| 73 | + return cb2(null, {file, slug, title, synopsis}); |
| 74 | + }); |
| 75 | + return fs.readdir(path(`${file}/pages`), (err, slugs) => asyncLib.map(slugs||[], synopsis, function(err, pages) { |
| 76 | + site.pages = pages; return cb(); |
| 77 | + })); |
| 78 | + }; |
| 79 | + const packagejson = cb => jsonfile.readFile(path(`${file}/package.json`), {throws:false}, function(err, packagejson) { |
| 80 | + site.package = packagejson; return cb(); |
| 81 | + }); |
| 82 | + const factory = cb => jsonfile.readFile(path(`${file}/factory.json`), {throws:false}, function(err, factory) { |
| 83 | + site.factory = factory; return cb(); |
| 84 | + }); |
| 85 | + const authors = cb => fs.readFile(path(`${file}/AUTHORS.txt`), 'utf-8', function(err, authors) { |
| 86 | + site.authors = authors; return cb(); |
| 87 | + }); |
| 88 | + // persona = (cb) -> |
| 89 | + // fs.readFile path("#{file}/status/persona.identity"),'utf8', (err, identity) -> |
| 90 | + // site.persona = identity; cb() |
| 91 | + // openid = (cb) -> |
| 92 | + // fs.readFile path("#{file}/status/open_id.identity"),'utf8', (err, identity) -> |
| 93 | + // site.openid = identity; cb() |
| 94 | + return asyncLib.series([birth,authors,packagejson,factory,pages], err => done(null, site)); |
| 95 | + }; |
| 96 | + |
| 97 | + const plugmap = done => glob("wiki-plugin-*", {cwd: argv.packageDir}, function(err, files) { |
| 98 | + if (err) { return done(err,null); } |
| 99 | + return asyncLib.map(files||[], info, function(err, install) { |
| 100 | + if (err) { return done(err,null); } |
| 101 | + return done(null, install); |
| 102 | + }); |
| 103 | + }); |
| 104 | + |
| 105 | + |
| 106 | + const view = function(plugin, done) { |
| 107 | + if (/^\w+$/.test(plugin)) { |
| 108 | + const pkg = `wiki-plugin-${plugin}`; |
| 109 | + return execFile('npm', ['view', `${pkg}`, '--json'], function(err, stdout, stderr) { |
| 110 | + let npm; |
| 111 | + try { npm = JSON.parse(stdout); } catch (error) {} |
| 112 | + return done(null, {plugin, pkg, npm}); |
| 113 | + }); |
| 114 | + } |
| 115 | + }; |
| 116 | + |
| 117 | + const farm = function(req, res, next) { |
| 118 | + if (argv.f) { |
| 119 | + return next(); |
| 120 | + } else { |
| 121 | + return res.status(404).send({error: 'service requires farm mode'}); |
| 122 | + } |
| 123 | + }; |
| 124 | + |
| 125 | + var admin = function(req, res, next) { |
| 126 | + if (app.securityhandler.isAdmin(req)) { |
| 127 | + return next(); |
| 128 | + } else { |
| 129 | + let user; |
| 130 | + if (!argv.admin) { admin = "none specified"; } |
| 131 | + if (!req.session?.passport?.user && !req.session?.email && !req.session?.friend) { user = "not logged in"; } |
| 132 | + return res.status(403).send({error: 'service requires admin user', admin, user}); |
| 133 | + } |
| 134 | + }; |
| 135 | + |
| 136 | + app.get(route('page/:slug.json'), (req, res) => plugmap(function(err, install) { |
| 137 | + for (var i of Array.from(install)) { |
| 138 | + for (var p of Array.from(i.pages)) { |
| 139 | + if (p.slug === req.params.slug) { |
| 140 | + return jsonfile.readFile(path(`${p.file}/pages/${p.slug}`), {throws:false}, (err, page) => res.json(page)); |
| 141 | + } |
| 142 | + } |
| 143 | + } |
| 144 | + return res.sendStatus(404); |
| 145 | + })); |
| 146 | + |
| 147 | + app.get(route('file/:file/slug/:slug'), (req, res) => jsonfile.readFile(path(`${req.params.file}/pages/${req.params.slug}`), {throws:false}, function(err, page) { |
| 148 | + if (err) { |
| 149 | + return res.sendStatus(404); |
| 150 | + } else { |
| 151 | + return res.json(page); |
| 152 | + } |
| 153 | + })); |
| 154 | + |
| 155 | + app.get(route('sitemap.json'), (req, res) => plugmap((err, install) => // http://stackoverflow.com/a/4631593 |
| 156 | + res.json( |
| 157 | + [].concat(...Array.from(((Array.from(install).map((i) => i.pages))) || [])) |
| 158 | + ))); |
| 159 | + |
| 160 | + app.get(route('plugins'), (req, res) => glob("wiki-plugin-*", {cwd: argv.packageDir}, function(err, files) { |
| 161 | + if (err) { return res.e(err); } |
| 162 | + return asyncLib.map(files||[], info, function(err, install) { |
| 163 | + if (err) { return res.e(err); } |
| 164 | + return res.json({install, bundle}); |
| 165 | + }); |
| 166 | +})); |
| 167 | + |
| 168 | + app.post(route('plugins'), function(req, res) { |
| 169 | + const payload = {bundle}; |
| 170 | + |
| 171 | + const installed = function(cb) { |
| 172 | + const files = (Array.from(req.body.plugins||[]).map((plugin) => `wiki-plugin-${plugin}`)); |
| 173 | + return asyncLib.map(files||[], info, function(err, install) { |
| 174 | + payload.install = install; return cb(); |
| 175 | + }); |
| 176 | + }; |
| 177 | + |
| 178 | + const published = cb => asyncLib.map(req.body.plugins||[], view, function(err, results) { |
| 179 | + payload.publish = results; return cb(); |
| 180 | + }); |
| 181 | + |
| 182 | + return asyncLib.parallel([installed, published], err => res.json(payload)); |
| 183 | + }); |
| 184 | + |
| 185 | + app.get(route('view/:pkg'), function(req, res) { |
| 186 | + if (/^\w+$/.test(req.params.pkg)) { |
| 187 | + const pkg = `wiki-plugin-${req.params.pkg}`; |
| 188 | + res.setHeader('Content-Type', 'application/json'); |
| 189 | + return execFile('npm', ['view', `${pkg}`, '--json']).stdout.pipe(res); |
| 190 | + } |
| 191 | + }); |
| 192 | + |
| 193 | + app.post(route('install'), admin, function(req, res) { |
| 194 | + if (/^\w+$/.test(req.body.plugin) && /^[\w.-]+$/.test(req.body.version)) { |
| 195 | + const pkg = `wiki-plugin-${req.body.plugin}@${req.body.version}`; |
| 196 | + console.log(`plugmatic installing ${pkg}`); |
| 197 | + return execFile('npm', ['install', `${pkg}`, '--json'], {cwd: argv.packageDir+'/..'}, function(err, stdout, stderr) { |
| 198 | + let npm; |
| 199 | + try { npm = JSON.parse(stdout); } catch (error) {} |
| 200 | + if (err) { |
| 201 | + return res.status(400).json({error: 'server unable to install plugin', npm, stderr}); |
| 202 | + } else { |
| 203 | + return info(`wiki-plugin-${req.body.plugin}`, (err, row) => res.json({installed: req.body.version, npm, stderr, row})); |
| 204 | + } |
| 205 | + }); |
| 206 | + } |
| 207 | +}); |
| 208 | + |
| 209 | + return app.post(route('restart'), admin, function(req, res) { |
| 210 | + console.log('plugmatic exit to restart'); |
| 211 | + res.sendStatus(200); |
| 212 | + return process.exit(0); |
| 213 | + }); |
| 214 | +}; |
| 215 | + |
| 216 | + |
| 217 | +module.exports = {startServer}; |
0 commit comments