Skip to content

Commit d654b34

Browse files
authored
Merge pull request #2680 from sveltejs/site/database-with-fallback
add database entries for new gists, update REPL URLs
2 parents 2e6f9c4 + f6fd1e2 commit d654b34

File tree

14 files changed

+420
-355
lines changed

14 files changed

+420
-355
lines changed

site/migrations/000-create-users.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ exports.up = DB => {
66
name character varying(255),
77
username character varying(255) not null,
88
avatar text,
9-
github_token character varying(255) not null,
9+
github_token character varying(255),
1010
created_at timestamp with time zone NOT NULL DEFAULT now(),
1111
updated_at timestamp with time zone
1212
);

site/package-lock.json

Lines changed: 5 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

site/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
"testsrc": "mocha -r esm test/**"
1616
},
1717
"dependencies": {
18+
"@polka/redirect": "^1.0.0-next.0",
1819
"@polka/send": "^1.0.0-next.2",
1920
"devalue": "^1.1.0",
2021
"do-not-zip": "^1.0.0",

site/src/components/Repl/ReplWidget.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@
2828
}
2929
3030
if (gist) {
31-
fetch(`gist/${gist}`).then(r => r.json()).then(data => {
31+
fetch(`repl/${gist}.json`).then(r => r.json()).then(data => {
3232
const { id, description, files } = data;
3333
3434
name = description;

site/src/routes/examples/[slug].json.js

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -6,16 +6,16 @@ const cache = new Map();
66
export function get(req, res) {
77
const { slug } = req.params;
88

9-
try {
10-
let example = cache.get(slug);
9+
let example = cache.get(slug);
1110

12-
if (!example || process.env.NODE_ENV !== 'production') {
13-
example = get_example(slug);
14-
cache.set(slug, example);
15-
}
11+
if (!example || process.env.NODE_ENV !== 'production') {
12+
example = get_example(slug);
13+
if (example) cache.set(slug, example);
14+
}
1615

16+
if (example) {
1717
send(res, 200, example);
18-
} catch (err) {
18+
} else {
1919
send(res, 404, {
2020
error: 'not found'
2121
});

site/src/routes/examples/_examples.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ export function get_example(slug) {
3535
const dir = lookup.get(slug);
3636
const title = titles.get(slug);
3737

38-
if (!dir || !title) throw { status: 404, message: 'not found' };
38+
if (!dir || !title) return null;
3939

4040
const files = fs.readdirSync(`content/examples/${dir}`)
4141
.filter(name => name[0] !== '.' && name !== 'meta.json')

site/src/routes/gist/[id].js

Lines changed: 0 additions & 65 deletions
This file was deleted.

site/src/routes/repl/_components/AppControls/UserMenu.svelte renamed to site/src/routes/repl/[id]/_components/AppControls/UserMenu.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<script>
2-
import { user, logout } from '../../../../user.js';
2+
import { user, logout } from '../../../../../user.js';
33
44
let showMenu = false;
55
let name;

site/src/routes/repl/_components/AppControls/index.svelte renamed to site/src/routes/repl/[id]/_components/AppControls/index.svelte

Lines changed: 31 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@
33
import UserMenu from './UserMenu.svelte';
44
import { Icon } from '@sveltejs/site-kit';
55
import * as doNotZip from 'do-not-zip';
6-
import downloadBlob from '../../_utils/downloadBlob.js';
7-
import { user } from '../../../../user.js';
8-
import { enter } from '../../../../utils/events.js';
9-
import { isMac } from '../../../../utils/compat.js';
6+
import downloadBlob from '../../../_utils/downloadBlob.js';
7+
import { user } from '../../../../../user.js';
8+
import { enter } from '../../../../../utils/events.js';
9+
import { isMac } from '../../../../../utils/compat.js';
1010
1111
const dispatch = createEventDispatcher();
1212
@@ -54,12 +54,15 @@
5454
const { components } = repl.toJSON();
5555
5656
try {
57-
const r = await fetch(`gist/create`, {
57+
const r = await fetch(`repl/create.json`, {
5858
method: 'POST',
5959
headers: { Authorization },
6060
body: JSON.stringify({
6161
name,
62-
components
62+
files: components.map(component => ({
63+
name: `${component.name}.${component.type}`,
64+
source: component.source
65+
}))
6366
})
6467
});
6568
@@ -107,16 +110,16 @@
107110
const files = {};
108111
const { components } = repl.toJSON();
109112
110-
components.forEach(module => {
111-
const text = module.source.trim();
112-
if (!text.length) return; // skip empty file
113-
files[`${module.name}.${module.type}`] = text;
114-
});
115-
116-
const r = await fetch(`gist/${gist.uid}`, {
113+
const r = await fetch(`repl/${gist.uid}.json`, {
117114
method: 'PATCH',
118115
headers: { Authorization },
119-
body: JSON.stringify({ name, files })
116+
body: JSON.stringify({
117+
name,
118+
files: components.map(component => ({
119+
name: `${component.name}.${component.type}`,
120+
source: component.source
121+
}))
122+
})
120123
});
121124
122125
if (r.status < 200 || r.status >= 300) {
@@ -197,29 +200,21 @@ export default app;` });
197200
<Icon name="download" />
198201
</button>
199202

200-
{#if $user}
201-
<button class="icon" disabled="{saving || !$user}" on:click={fork} title="fork">
202-
{#if justForked}
203-
<Icon name="check" />
204-
{:else}
205-
<Icon name="git-branch" />
206-
{/if}
207-
</button>
208-
209-
<button class="icon" disabled="{saving || !$user}" on:click={save} title="save">
210-
{#if justSaved}
211-
<Icon name="check" />
212-
{:else}
213-
<Icon name="save" />
214-
{/if}
215-
</button>
216-
{/if}
203+
<button class="icon" disabled="{saving || !$user}" on:click={() => fork(false)} title="fork">
204+
{#if justForked}
205+
<Icon name="check" />
206+
{:else}
207+
<Icon name="git-branch" />
208+
{/if}
209+
</button>
217210

218-
{#if gist}
219-
<a class="icon no-underline" href={gist.html_url} title="link to gist">
220-
<Icon name="link" />
221-
</a>
222-
{/if}
211+
<button class="icon" disabled="{saving || !$user}" on:click={save} title="save">
212+
{#if justSaved}
213+
<Icon name="check" />
214+
{:else}
215+
<Icon name="save" />
216+
{/if}
217+
</button>
223218

224219
{#if $user}
225220
<UserMenu />
Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,135 @@
1+
import send from '@polka/send';
2+
import redirect from '@polka/redirect';
3+
import body from '../_utils/body.js';
4+
import * as httpie from 'httpie';
5+
import { query, find } from '../../../utils/db';
6+
import { isUser } from '../../../backend/auth';
7+
import { get_example } from '../../examples/_examples.js';
8+
9+
const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } = process.env;
10+
11+
async function import_gist(req, res) {
12+
const base = `https://api.github.com/gists/${req.params.id}`;
13+
const url = `${base}?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}`;
14+
15+
try {
16+
const { data } = await httpie.get(url, {
17+
headers: {
18+
'User-Agent': 'https://svelte.dev'
19+
}
20+
});
21+
22+
// create owner if necessary...
23+
let user = await find(`select * from users where uid = $1`, [data.owner.id]);
24+
25+
if (!user) {
26+
const { id, name, login, avatar_url } = data.owner;
27+
28+
[user] = await query(`
29+
insert into users(uid, name, username, avatar)
30+
values ($1, $2, $3, $4)
31+
returning *
32+
`, [id, name, login, avatar_url]);
33+
}
34+
35+
delete data.files['README.md'];
36+
delete data.files['meta.json'];
37+
38+
const files = Object.keys(data.files).map(key => {
39+
const name = key.replace(/\.html$/, '.svelte');
40+
41+
return {
42+
name,
43+
source: data.files[key].content
44+
};
45+
});
46+
47+
// add gist to database...
48+
const [gist] = await query(`
49+
insert into gists(uid, user_id, name, files)
50+
values ($1, $2, $3, $4) returning *`, [req.params.id, user.id, data.description, JSON.stringify(files)]);
51+
52+
send(res, 200, {
53+
uid: req.params.id,
54+
name: data.description,
55+
files,
56+
owner: data.owner.id
57+
});
58+
} catch (err) {
59+
send(res, err.statusCode, { error: err.message });
60+
}
61+
}
62+
63+
export async function get(req, res) {
64+
// is this an example?
65+
const example = get_example(req.params.id);
66+
67+
if (example) {
68+
return send(res, 200, {
69+
relaxed: true,
70+
uid: req.params.id,
71+
name: example.title,
72+
files: example.files,
73+
owner: null
74+
});
75+
}
76+
77+
const [row] = await query(`
78+
select g.*, u.uid as owner from gists g
79+
left join users u on g.user_id = u.id
80+
where g.uid = $1 limit 1
81+
`, [req.params.id]); // via filename pattern
82+
83+
if (!row) {
84+
return import_gist(req, res);
85+
}
86+
87+
send(res, 200, {
88+
uid: row.uid.replace(/-/g, ''),
89+
name: row.name,
90+
files: row.files,
91+
owner: row.owner
92+
});
93+
}
94+
95+
export async function patch(req, res) {
96+
const user = await isUser(req, res);
97+
if (!user) return; // response already sent
98+
99+
let id, uid=req.params.id;
100+
101+
try {
102+
const [row] = await query(`select * from gists where uid = $1 limit 1`, [uid]);
103+
if (!row) return send(res, 404, { error: 'Gist not found' });
104+
if (row.user_id !== user.id) return send(res, 403, { error: 'Item does not belong to you' });
105+
id = row.id;
106+
} catch (err) {
107+
console.error('PATCH /gists @ select', err);
108+
return send(res, 500);
109+
}
110+
111+
try {
112+
const obj = await body(req);
113+
obj.updated_at = 'now()';
114+
let k, cols=[], vals=[];
115+
for (k in obj) {
116+
cols.push(k);
117+
vals.push(k === 'files' ? JSON.stringify(obj[k]) : obj[k]);
118+
}
119+
120+
const tmp = vals.map((x, i) => `$${i + 1}`).join(',');
121+
const set = `set (${cols.join(',')}) = (${tmp})`;
122+
123+
const [row] = await query(`update gists ${set} where id = ${id} returning *`, vals);
124+
125+
send(res, 200, {
126+
uid: row.uid.replace(/-/g, ''),
127+
name: row.name,
128+
files: row.files,
129+
owner: user.uid,
130+
});
131+
} catch (err) {
132+
console.error('PATCH /gists @ update', err);
133+
send(res, 500, { error: err.message });
134+
}
135+
}

0 commit comments

Comments
 (0)