From 7390bcdfb556bea8344fcb4ad492ec8150b43f20 Mon Sep 17 00:00:00 2001 From: Rich Harris Date: Fri, 21 Dec 2018 13:54:52 -0500 Subject: [PATCH] save gists --- site/package-lock.json | 5 ++ site/package.json | 1 + site/src/routes/gist/[id].js | 73 ++++++++++++++++++ site/src/routes/gist/_utils.js | 19 +++++ site/src/routes/gist/create.js | 74 +++++++++++++++++++ .../repl/_components/AppControls/index.html | 2 +- site/src/routes/repl/_components/Repl.html | 14 ++++ 7 files changed, 187 insertions(+), 1 deletion(-) create mode 100644 site/src/routes/gist/[id].js create mode 100644 site/src/routes/gist/_utils.js create mode 100644 site/src/routes/gist/create.js diff --git a/site/package-lock.json b/site/package-lock.json index d66ae940bf..56a65f6fd7 100644 --- a/site/package-lock.json +++ b/site/package-lock.json @@ -3033,6 +3033,11 @@ "lower-case": "^1.1.1" } }, + "node-fetch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.3.0.tgz", + "integrity": "sha512-MOd8pV3fxENbryESLgVIeaGKrdl+uaYhCSSVkjeOb/31/njTpcis5aWfdqgNlHIrKOLRbMnfPINPOML2CIFeXA==" + }, "node-releases": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.1.tgz", diff --git a/site/package.json b/site/package.json index 7489cc5c3b..5fc6c4e814 100644 --- a/site/package.json +++ b/site/package.json @@ -24,6 +24,7 @@ "express-session": "^1.15.6", "golden-fleece": "^1.0.9", "marked": "^0.5.2", + "node-fetch": "^2.3.0", "passport": "^0.4.0", "passport-github": "^1.1.0", "prismjs": "^1.15.0", diff --git a/site/src/routes/gist/[id].js b/site/src/routes/gist/[id].js new file mode 100644 index 0000000000..91a362e2aa --- /dev/null +++ b/site/src/routes/gist/[id].js @@ -0,0 +1,73 @@ +import fetch from 'node-fetch'; +import { body } from './_utils.js'; + +export async function get(req, res) { + const { id } = req.params; + + const r = await fetch(`https://api.github.com/gists/${id}`); + + res.writeHead(r.status, { + 'Content-Type': 'application/json' + }); + + const result = await r.json(); + + if (r.status === 200) { + res.end(JSON.stringify({ + id: result.id, + description: result.description, + owner: result.owner, + html_url: result.html_url, + files: result.files + })); + } else { + res.end(JSON.stringify(result)); + } +} + +export async function patch(req, res) { + const user = req.session.passport && req.session.passport.user; + + if (!user) { + res.writeHead(403, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ error: 'unauthorized' })); + return; + } + + try { + const { description, files } = await body(req); + + const r = await fetch(`https://api.github.com/gists/${req.params.id}`, { + method: 'PATCH', + headers: { + Authorization: `token ${user.token}` + }, + body: JSON.stringify({ + description, + files + }) + }); + + res.writeHead(r.status, { + 'Content-Type': 'application/json' + }); + + if (r.status === 200) { + res.end(JSON.stringify({ + ok: true + })); + } else { + res.end(await r.text()); + } + } catch (err) { + res.writeHead(500, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify({ + error: err.message + })); + } +} \ No newline at end of file diff --git a/site/src/routes/gist/_utils.js b/site/src/routes/gist/_utils.js new file mode 100644 index 0000000000..8417031001 --- /dev/null +++ b/site/src/routes/gist/_utils.js @@ -0,0 +1,19 @@ +export function body(req) { + return new Promise((fulfil, reject) => { + let str = ''; + + req.on('error', reject); + + req.on('data', chunk => { + str += chunk; + }); + + req.on('end', () => { + try { + fulfil(JSON.parse(str)); + } catch (err) { + reject(err); + } + }); + }); +} \ No newline at end of file diff --git a/site/src/routes/gist/create.js b/site/src/routes/gist/create.js new file mode 100644 index 0000000000..601f21d466 --- /dev/null +++ b/site/src/routes/gist/create.js @@ -0,0 +1,74 @@ +import fetch from 'node-fetch'; +import { body } from './_utils.js'; + +export async function post(req, res) { + const user = req.session.passport && req.session.passport.user; + + if (!user) { + res.writeHead(403, { + 'Content-Type': 'application/json' + }); + res.end(JSON.stringify({ error: 'unauthorized' })); + return; + } + + try { + const { name, components, json5 } = await body(req); + + const files = { + 'meta.json': { + content: JSON.stringify({ + svelte: true + }, null, ' ') + }, + 'README.md': { + content: `Created with [svelte.technology/repl](https://svelte.technology/repl)` + } + }; + components.forEach(component => { + const file = `${component.name}.${component.type}`; + if (!component.source.trim()) { + throw new Error(`GitHub does not allow saving gists with empty files - ${file}`); + } + files[file] = { content: component.source }; + }); + + if (json5) { + files['data.json5'] = { content: json5 }; + } + + const r = await fetch(`https://api.github.com/gists`, { + method: 'POST', + headers: { + Authorization: `token ${user.token}` + }, + body: JSON.stringify({ + description: name, + files, + public: false + }) + }); + + res.writeHead(r.status, { + 'Content-Type': 'application/json' + }); + + const gist = await r.json(); + + res.end(JSON.stringify({ + id: gist.id, + description: gist.description, + owner: gist.owner, + html_url: gist.html_url, + files: gist.files + })); + } catch (err) { + res.writeHead(500, { + 'Content-Type': 'application/json' + }); + + res.end(JSON.stringify({ + error: err.message + })); + } +} diff --git a/site/src/routes/repl/_components/AppControls/index.html b/site/src/routes/repl/_components/AppControls/index.html index 3dfab728d0..8159c19654 100644 --- a/site/src/routes/repl/_components/AppControls/index.html +++ b/site/src/routes/repl/_components/AppControls/index.html @@ -1,4 +1,5 @@