Skip to content

Commit

Permalink
feat: scripts
Browse files Browse the repository at this point in the history
  • Loading branch information
eliassjogreen committed Jun 14, 2023
1 parent 301c885 commit b719d0c
Show file tree
Hide file tree
Showing 23 changed files with 480 additions and 76 deletions.
7 changes: 2 additions & 5 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,10 @@
"deno.lint": true,
"deno.unstable": true,
"editor.defaultFormatter": "denoland.vscode-deno",
"[typescriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[typescript]": {
"editor.defaultFormatter": "denoland.vscode-deno"
},
"[css]": {
"editor.defaultFormatter": "vscode.css-language-features"
"[typescriptreact]": {
"editor.defaultFormatter": "denoland.vscode-deno"
}
}
12 changes: 6 additions & 6 deletions components/Content.tsx
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import Features from "./Features.tsx";
import Features from "~/components/Features.tsx";

export function Content() {
return (
<div class="mt-4 pb-8 flex justify-center rounded-lg text-black text-1xl lg:text-1xl bg-primary">
<Features/>
</div>
);
return (
<div class="mt-4 pb-8 flex justify-center rounded-lg text-black text-1xl lg:text-1xl bg-primary">
<Features />
</div>
);
}
44 changes: 31 additions & 13 deletions components/Header.tsx
Original file line number Diff line number Diff line change
@@ -1,30 +1,48 @@
import { ComponentChildren } from "preact";
import Button from "~/islands/Button.tsx";

export function Navigation() {
export interface NavigationProps {
// role: UserRole;
authenticated: boolean;
}

export function Navigation({ authenticated }: NavigationProps) {
return (
<nav class="flex justify-between font-bold text-1xl lg:text-2xl">
<a class="hover:(animate-once animate-pulse)" href="">ALIAS</a>
<a class="hover:(animate-once animate-pulse)" href="">API</a>
<a class="hover:(animate-once animate-pulse)" href="">ADMIN</a>
<a class="hover:(animate-once animate-pulse)" href="">LOGIN</a>
<a class="hover:(animate-once animate-pulse)" href="">Alias</a>
{/* <a class="hover:(animate-once animate-pulse)" href="">API</a> */}
{/* <a class="hover:(animate-once animate-pulse)" href="">ADMIN</a> */}
<a
class="hover:(animate-once animate-pulse)"
href={authenticated ? "/api/auth/logout" : "/api/auth/github/login"}
>
{authenticated ? "Logout" : "Login"}
</a>
</nav>
)
);
}

// This should be an island
export function Upload() {
return (
<button>
UPLOAD
</button>
)
<div class="flex items-center justify-center">
<Button />
</div>
);
}

export interface HeaderProps {
// role: UserRole;
authenticated: boolean;
}

export function Header() {
export function Header({ authenticated }: HeaderProps) {
return (
<header class="flex flex-col sticky top-0 w-full bg-secondary z-50 pb-4 gap-4 border-b-2 border-primary">
<div class="flex justify-center uppercase tracking-tighter font-bold text-4xl lg:text-8xl animate-jump">crux.land</div>
<Navigation />
<div class="flex justify-center uppercase tracking-tighter font-bold text-4xl lg:text-8xl animate-jump">
crux.land
</div>
<Navigation authenticated={authenticated} />
<Upload />
</header>
);
Expand Down
51 changes: 32 additions & 19 deletions fresh.gen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,29 +3,42 @@
// This file is automatically updated during development when running `dev.ts`.

import config from "./deno.json" assert { type: "json" };
import * as $0 from "./routes/api/_middleware.ts";
import * as $1 from "./routes/api/auth/github/callback.ts";
import * as $2 from "./routes/api/auth/github/login.ts";
import * as $3 from "./routes/api/auth/logout/_middleware.ts";
import * as $4 from "./routes/api/auth/logout/index.ts";
import * as $5 from "./routes/api/script/index.ts";
import * as $6 from "./routes/api/user/_middleware.ts";
import * as $7 from "./routes/api/user/index.ts";
import * as $8 from "./routes/index.tsx";
import * as $0 from "./routes/.well-known/deno-import-intellisense.json.ts";
import * as $1 from "./routes/[alias]@[version].tsx";
import * as $2 from "./routes/[id].tsx";
import * as $3 from "./routes/_middleware.ts";
import * as $4 from "./routes/api/auth/github/callback.ts";
import * as $5 from "./routes/api/auth/github/login.ts";
import * as $6 from "./routes/api/auth/logout/_middleware.ts";
import * as $7 from "./routes/api/auth/logout/index.ts";
import * as $8 from "./routes/api/script/[id].ts";
import * as $9 from "./routes/api/script/index.ts";
import * as $10 from "./routes/api/scripts.ts";
import * as $11 from "./routes/api/user/_middleware.ts";
import * as $12 from "./routes/api/user/index.ts";
import * as $13 from "./routes/index.tsx";
import * as $$0 from "./islands/Button.tsx";

const manifest = {
routes: {
"./routes/api/_middleware.ts": $0,
"./routes/api/auth/github/callback.ts": $1,
"./routes/api/auth/github/login.ts": $2,
"./routes/api/auth/logout/_middleware.ts": $3,
"./routes/api/auth/logout/index.ts": $4,
"./routes/api/script/index.ts": $5,
"./routes/api/user/_middleware.ts": $6,
"./routes/api/user/index.ts": $7,
"./routes/index.tsx": $8,
"./routes/.well-known/deno-import-intellisense.json.ts": $0,
"./routes/[alias]@[version].tsx": $1,
"./routes/[id].tsx": $2,
"./routes/_middleware.ts": $3,
"./routes/api/auth/github/callback.ts": $4,
"./routes/api/auth/github/login.ts": $5,
"./routes/api/auth/logout/_middleware.ts": $6,
"./routes/api/auth/logout/index.ts": $7,
"./routes/api/script/[id].ts": $8,
"./routes/api/script/index.ts": $9,
"./routes/api/scripts.ts": $10,
"./routes/api/user/_middleware.ts": $11,
"./routes/api/user/index.ts": $12,
"./routes/index.tsx": $13,
},
islands: {
"./islands/Button.tsx": $$0,
},
islands: {},
baseUrl: import.meta.url,
config,
};
Expand Down
7 changes: 7 additions & 0 deletions islands/Button.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
export default function Button() {
return (
<button class="btn px-24 py-4 bg-primary text-secondary rounded hover:(animate-once animate-pulse)">
UPLOAD
</button>
);
}
5 changes: 4 additions & 1 deletion models/alias.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,10 @@ export async function createAlias(alias: Alias): Promise<Alias> {
const res = await kv
.atomic()
.check({ key: ["alias", alias.name], versionstamp: null })
.check({ key: ["alias_by_user", alias.owner, alias.name], versionstamp: null })
.check({
key: ["alias_by_user", alias.owner, alias.name],
versionstamp: null,
})
.set(["alias", alias.name], alias)
.set(["alias_by_user", alias.owner, alias.name], alias)
.commit();
Expand Down
42 changes: 36 additions & 6 deletions models/script.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,25 @@
import { kv } from "~/utils/kv.ts";
import { EXTENSION_FROM_CONTENT_TYPE } from "../utils/constants.ts";

export interface Script {
id: string;
author?: string;
code: Uint8Array;
contentType: keyof typeof EXTENSION_FROM_CONTENT_TYPE;
content: string;
extension: string;
contentType: string;
}

export async function createScript(script: Script): Promise<Script> {
const res = await kv
const operation = kv
.atomic()
.check({ key: ["script", script.id], versionstamp: null })
.set(["script", script.id], script)
.commit();
.set(["script", script.id], script);

if (script.author) {
operation
.set(["script_by_user", script.author, script.id], script);
}

const res = await operation.commit();

if (!res.ok) {
throw new Error(`Script with id ${script.id} already exists`);
Expand All @@ -30,3 +36,27 @@ export async function getScript(id: string): Promise<Script | null> {
(await kv.get<Script>(key, { consistency: "strong" })).value
);
}

export async function listScripts(): Promise<Script[]> {
const sessions = [];

for await (
const session of kv.list<Script>({ prefix: ["script"] })
) {
sessions.push(session.value);
}

return sessions;
}

export async function listScriptsByUser(user: string): Promise<Script[]> {
const sessions = [];

for await (
const session of kv.list<Script>({ prefix: ["script_by_user", user] })
) {
sessions.push(session.value);
}

return sessions;
}
2 changes: 1 addition & 1 deletion models/user.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ export async function getUser(id: string): Promise<User | null> {

export async function getUserByName(name: string): Promise<User | null> {
const id = (await kv.get<string>(["user_by_name", name])).value;

if (id == null) {
return null;
}
Expand Down
10 changes: 10 additions & 0 deletions routes/.well-known/deno-import-intellisense.json.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { Handlers, Status } from "$fresh/server.ts";

import { SessionState } from "~/middlewares/session.ts";
import { getScript } from "~/models/script.ts";

export const handler: Handlers<unknown, SessionState> = {
async GET(_req, _ctx) {
return Response.json({});
},
};
Empty file added routes/[alias]@[version].tsx
Empty file.
81 changes: 81 additions & 0 deletions routes/[id].tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
import { Handlers, Status } from "$fresh/server.ts";
import { Head } from "$fresh/runtime.ts";

import { SessionState } from "~/middlewares/session.ts";

import { getScript, Script } from "~/models/script.ts";
import { Session } from "~/models/session.ts";

import { Footer } from "~/components/Footer.tsx";
import { Header } from "~/components/Header.tsx";
import { Layout } from "~/components/Layout.tsx";

export interface ScriptPageProps {
session?: Session;
script: Script;
}

export const handler: Handlers<ScriptPageProps, SessionState> = {
async GET(req, ctx) {
const isHtml = req.headers.get("accept")?.includes("html");
const script = await getScript(ctx.params.id);

if (isHtml) {
if (script == null) {
// return ctx.renderNotFound();
return ctx.render("hi");
}
return ctx.render({ session: ctx.state.session, script });
}

if (script == null) {
return Response.json(
{
message: `Could not find script with id ${ctx.params.id}`,
},
{
status: Status.NotFound,
},
);
}

return new Response(script.content, {
status: Status.OK,
headers: new Headers({
"Content-Type": `${script.contentType!}; charset=utf-8`,
}),
});
},
};

export default function Script({
data: { script },
}: {
data: { script: Script };
}) {
return (
<>
<Head>
<meta charSet="UTF-8" />
<title>
crux.land | Registry for permanently hosting small scripts
</title>
<meta content="Crux.land" property="og:title" />
<meta
content="Free registry service meant for hosting small (≤ 20kB) single deno scripts."
property="og:description"
/>
<meta content="https://crux.land" property="og:url" />
<meta content="#3f72a0" data-react-helmet="true" name="theme-color" />
<link rel="stylesheet" href="styles.css" />
</Head>
<Layout>
<Header authenticated={false} />
<div class="mt-4 pb-8 flex rounded-lg text-black text-1xl lg:text-1xl bg-primary">
<code>{script.content}</code>
</div>
<Footer />
</Layout>
</>
);
}
File renamed without changes.
5 changes: 3 additions & 2 deletions routes/api/auth/github/callback.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
import { HandlerContext, Handlers, Status } from "$fresh/server.ts";

import { loginState } from "~/utils/auth.ts";
import { fetchAccessToken, fetchUserData } from "~/utils/github.ts";
import { createUser, getUser } from "~/models/user.ts";
import { createSession } from "~/models/session.ts";

import { loginState } from "~/utils/auth.ts";
import { fetchAccessToken, fetchUserData } from "~/utils/github.ts";
import { setSessionCookie } from "~/utils/session.ts";

export const handler: Handlers = {
Expand Down
14 changes: 13 additions & 1 deletion routes/api/auth/logout/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,19 @@ import { Handlers, Status } from "$fresh/server.ts";
import { AuthenticatedState } from "~/middlewares/authenticated.ts";

export const handler: Handlers<unknown, AuthenticatedState> = {
async GET(_req: Request, ctx) {
async GET(req: Request, ctx) {
const isHtml = req.headers.get("accept")?.includes("html");
const redirect = req.headers.get("referer") ?? isHtml ? "/" : null;

if (redirect != null) {
return new Response(null, {
status: Status.Found,
headers: new Headers({
"Location": redirect,
}),
});
}

await ctx.state.session.delete();
return new Response(null, { status: Status.OK });
},
Expand Down
20 changes: 20 additions & 0 deletions routes/api/script/[id].ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Handlers, Status } from "$fresh/server.ts";

import { SessionState } from "~/middlewares/session.ts";
import { getScript } from "~/models/script.ts";

export const handler: Handlers<{ id: string }, SessionState> = {
async GET(_req, ctx) {
const script = await getScript(ctx.params.id);

if (script == null) {
return Response.json({
message: `Could not find script with id ${ctx.params.id}`,
}, {
status: Status.NotFound,
});
}

return Response.json(script, { status: Status.OK });
},
};
Loading

1 comment on commit b719d0c

@deno-deploy
Copy link

@deno-deploy deno-deploy bot commented on b719d0c Jun 14, 2023

Choose a reason for hiding this comment

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

Failed to deploy:

Relative import path "$fresh/plugins/twind.ts" not prefixed with / or ./ or ../

Please sign in to comment.