Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add database entries for new gists, update REPL URLs #2680

Merged
merged 5 commits into from
May 5, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion site/migrations/000-create-users.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ exports.up = DB => {
name character varying(255),
username character varying(255) not null,
avatar text,
github_token character varying(255) not null,
github_token character varying(255),
Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This should be the only change to the production DB required

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Will do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done

created_at timestamp with time zone NOT NULL DEFAULT now(),
updated_at timestamp with time zone
);
Expand Down
5 changes: 5 additions & 0 deletions site/package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions site/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
"testsrc": "mocha -r esm test/**"
},
"dependencies": {
"@polka/redirect": "^1.0.0-next.0",
"@polka/send": "^1.0.0-next.2",
"devalue": "^1.1.0",
"do-not-zip": "^1.0.0",
Expand Down
2 changes: 1 addition & 1 deletion site/src/components/Repl/ReplWidget.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@
}

if (gist) {
fetch(`gist/${gist}`).then(r => r.json()).then(data => {
fetch(`repl/${gist}.json`).then(r => r.json()).then(data => {
const { id, description, files } = data;

name = description;
Expand Down
14 changes: 7 additions & 7 deletions site/src/routes/examples/[slug].json.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,16 +6,16 @@ const cache = new Map();
export function get(req, res) {
const { slug } = req.params;

try {
let example = cache.get(slug);
let example = cache.get(slug);

if (!example || process.env.NODE_ENV !== 'production') {
example = get_example(slug);
cache.set(slug, example);
}
if (!example || process.env.NODE_ENV !== 'production') {
example = get_example(slug);
if (example) cache.set(slug, example);
}

if (example) {
send(res, 200, example);
} catch (err) {
} else {
send(res, 404, {
error: 'not found'
});
Expand Down
2 changes: 1 addition & 1 deletion site/src/routes/examples/_examples.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ export function get_example(slug) {
const dir = lookup.get(slug);
const title = titles.get(slug);

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

const files = fs.readdirSync(`content/examples/${dir}`)
.filter(name => name[0] !== '.' && name !== 'meta.json')
Expand Down
65 changes: 0 additions & 65 deletions site/src/routes/gist/[id].js

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
<script>
import { user, logout } from '../../../../user.js';
import { user, logout } from '../../../../../user.js';

let showMenu = false;
let name;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import UserMenu from './UserMenu.svelte';
import { Icon } from '@sveltejs/site-kit';
import * as doNotZip from 'do-not-zip';
import downloadBlob from '../../_utils/downloadBlob.js';
import { user } from '../../../../user.js';
import { enter } from '../../../../utils/events.js';
import { isMac } from '../../../../utils/compat.js';
import downloadBlob from '../../../_utils/downloadBlob.js';
import { user } from '../../../../../user.js';
import { enter } from '../../../../../utils/events.js';
import { isMac } from '../../../../../utils/compat.js';

const dispatch = createEventDispatcher();

Expand Down Expand Up @@ -54,12 +54,15 @@
const { components } = repl.toJSON();

try {
const r = await fetch(`gist/create`, {
const r = await fetch(`repl/create.json`, {
method: 'POST',
headers: { Authorization },
body: JSON.stringify({
name,
components
files: components.map(component => ({
name: `${component.name}.${component.type}`,
source: component.source
}))
})
});

Expand Down Expand Up @@ -107,16 +110,16 @@
const files = {};
const { components } = repl.toJSON();

components.forEach(module => {
const text = module.source.trim();
if (!text.length) return; // skip empty file
files[`${module.name}.${module.type}`] = text;
});

const r = await fetch(`gist/${gist.uid}`, {
const r = await fetch(`repl/${gist.uid}.json`, {
method: 'PATCH',
headers: { Authorization },
body: JSON.stringify({ name, files })
body: JSON.stringify({
name,
files: components.map(component => ({
name: `${component.name}.${component.type}`,
source: component.source
}))
})
});

if (r.status < 200 || r.status >= 300) {
Expand Down Expand Up @@ -197,29 +200,21 @@ export default app;` });
<Icon name="download" />
</button>

{#if $user}
<button class="icon" disabled="{saving || !$user}" on:click={fork} title="fork">
{#if justForked}
<Icon name="check" />
{:else}
<Icon name="git-branch" />
{/if}
</button>

<button class="icon" disabled="{saving || !$user}" on:click={save} title="save">
{#if justSaved}
<Icon name="check" />
{:else}
<Icon name="save" />
{/if}
</button>
{/if}
<button class="icon" disabled="{saving || !$user}" on:click={() => fork(false)} title="fork">
{#if justForked}
<Icon name="check" />
{:else}
<Icon name="git-branch" />
{/if}
</button>

{#if gist}
<a class="icon no-underline" href={gist.html_url} title="link to gist">
<Icon name="link" />
</a>
{/if}
<button class="icon" disabled="{saving || !$user}" on:click={save} title="save">
{#if justSaved}
<Icon name="check" />
{:else}
<Icon name="save" />
{/if}
</button>

{#if $user}
<UserMenu />
Expand Down
135 changes: 135 additions & 0 deletions site/src/routes/repl/[id]/index.json.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,135 @@
import send from '@polka/send';
import redirect from '@polka/redirect';
import body from '../_utils/body.js';
import * as httpie from 'httpie';
import { query, find } from '../../../utils/db';
import { isUser } from '../../../backend/auth';
import { get_example } from '../../examples/_examples.js';

const { GITHUB_CLIENT_ID, GITHUB_CLIENT_SECRET } = process.env;

async function import_gist(req, res) {
const base = `https://api.github.com/gists/${req.params.id}`;
const url = `${base}?client_id=${GITHUB_CLIENT_ID}&client_secret=${GITHUB_CLIENT_SECRET}`;

try {
const { data } = await httpie.get(url, {
headers: {
'User-Agent': 'https://svelte.dev'
}
});

// create owner if necessary...
let user = await find(`select * from users where uid = $1`, [data.owner.id]);

if (!user) {
const { id, name, login, avatar_url } = data.owner;

[user] = await query(`
insert into users(uid, name, username, avatar)
values ($1, $2, $3, $4)
returning *
`, [id, name, login, avatar_url]);
}

delete data.files['README.md'];
delete data.files['meta.json'];

const files = Object.keys(data.files).map(key => {
const name = key.replace(/\.html$/, '.svelte');

return {
name,
source: data.files[key].content
};
});

// add gist to database...
const [gist] = await query(`
insert into gists(uid, user_id, name, files)
values ($1, $2, $3, $4) returning *`, [req.params.id, user.id, data.description, JSON.stringify(files)]);

send(res, 200, {
uid: req.params.id,
name: data.description,
files,
owner: data.owner.id
});
} catch (err) {
send(res, err.statusCode, { error: err.message });
}
}

export async function get(req, res) {
// is this an example?
const example = get_example(req.params.id);

if (example) {
return send(res, 200, {
relaxed: true,
uid: req.params.id,
name: example.title,
files: example.files,
owner: null
});
}

const [row] = await query(`
select g.*, u.uid as owner from gists g
left join users u on g.user_id = u.id
where g.uid = $1 limit 1
`, [req.params.id]); // via filename pattern

if (!row) {
return import_gist(req, res);
}

send(res, 200, {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: row.owner
});
}

export async function patch(req, res) {
const user = await isUser(req, res);
if (!user) return; // response already sent

let id, uid=req.params.id;

try {
const [row] = await query(`select * from gists where uid = $1 limit 1`, [uid]);
if (!row) return send(res, 404, { error: 'Gist not found' });
if (row.user_id !== user.id) return send(res, 403, { error: 'Item does not belong to you' });
id = row.id;
} catch (err) {
console.error('PATCH /gists @ select', err);
return send(res, 500);
}

try {
const obj = await body(req);
obj.updated_at = 'now()';
let k, cols=[], vals=[];
for (k in obj) {
cols.push(k);
vals.push(k === 'files' ? JSON.stringify(obj[k]) : obj[k]);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Did I write this? Can't tell if this file was moved or is new.

Looking at it, the JSON.stringify shouldn't be necessary... I think

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I added the JSON.stringify, it was erroring without it. I think it'll accept objects but not arrays (I changed the structure to files: [...] instead of files: {...} since we're no longer bound to the gist format, to reduce faffing

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ah, then okay. Arrays need explicit casting. We could also do { files } so that the input/output is automatic. Just have to remember to row.files.files, which may be too annoying.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

yeah, i wondered about that. i reckon explicit casting is ok, since we probably won't need to add it in more places, and since we'll get a loud error if we do

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I happy either way

}

const tmp = vals.map((x, i) => `$${i + 1}`).join(',');
const set = `set (${cols.join(',')}) = (${tmp})`;

const [row] = await query(`update gists ${set} where id = ${id} returning *`, vals);

send(res, 200, {
uid: row.uid.replace(/-/g, ''),
name: row.name,
files: row.files,
owner: user.uid,
});
} catch (err) {
console.error('PATCH /gists @ update', err);
send(res, 500, { error: err.message });
}
}
Loading