From 4c2d260e1bbf6d20dddabdf687ae9b0b29f42b0b Mon Sep 17 00:00:00 2001 From: d3rpp Date: Sun, 25 Aug 2024 11:28:19 +1200 Subject: [PATCH] Todays Progress --- .env.example | 3 + bun.lockb | Bin 191444 -> 191868 bytes drizzle/0001_premium_khan.sql | 4 +- package.json | 1 + src/lib/client/index.ts | 169 +++++++++--------- .../account/account-header-component.svelte | 2 +- src/lib/components/headers/index.ts | 2 +- src/lib/components/ui/sonner/index.ts | 1 + src/lib/components/ui/sonner/sonner.svelte | 25 +++ src/lib/icons/AnimatedLoading.svelte | 13 ++ src/lib/icons/index.ts | 5 +- src/lib/server/db.ts | 2 +- src/lib/server/trpc/context.ts | 4 + src/lib/server/trpc/middleware.ts | 23 +++ src/lib/server/trpc/router/auth.ts | 6 +- src/lib/server/trpc/router/greeter.ts | 2 +- src/lib/server/trpc/router/index.ts | 13 +- src/lib/server/trpc/router/user.ts | 15 ++ src/routes/(app)/app/+page.svelte | 18 +- src/routes/(app)/app/account/+page.svelte | 58 ++++-- src/routes/(app)/app/account/+page.ts | 14 ++ src/routes/(auth)/auth/login/+page.svelte | 8 +- src/routes/(auth)/auth/sign-up/+page.svelte | 22 +-- src/routes/(landing)/+page.svelte | 4 +- src/routes/+layout.svelte | 13 +- src/routes/+layout.ts | 4 +- 26 files changed, 289 insertions(+), 142 deletions(-) create mode 100644 src/lib/components/ui/sonner/index.ts create mode 100644 src/lib/components/ui/sonner/sonner.svelte create mode 100644 src/lib/icons/AnimatedLoading.svelte create mode 100644 src/lib/server/trpc/middleware.ts create mode 100644 src/lib/server/trpc/router/user.ts create mode 100644 src/routes/(app)/app/account/+page.ts diff --git a/.env.example b/.env.example index 55ac2c0..f850476 100644 --- a/.env.example +++ b/.env.example @@ -4,3 +4,6 @@ DATABASE_FILE=./database.db GITHUB_CLIENT_ID="" GITHUB_CLIENT_SECRET="" + +# Set this to 1 or true to enable query logging +LOGGER_ENABLED="" \ No newline at end of file diff --git a/bun.lockb b/bun.lockb index eb29c9d725aba9059ea8ec37bed9079ef89cfd59..352a0eaa514500b260297df4c596a8554ae9c3e8 100755 GIT binary patch delta 30550 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 ehp#P9?33sD);fJrFAKkqE9|GRHH*C=55 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 cPv{ACzF&>Ft1^QwSH statement-breakpoint CREATE TABLE `sessions` ( `id` text NOT NULL, `user_id` text NOT NULL, `expires_at` integer NOT NULL, - FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE no action + FOREIGN KEY (`user_id`) REFERENCES `users`(`id`) ON UPDATE no action ON DELETE cascade ); diff --git a/package.json b/package.json index ccf9fab..7cb8d23 100644 --- a/package.json +++ b/package.json @@ -56,6 +56,7 @@ "lucide-svelte": "^0.428.0", "mode-watcher": "^0.4.1", "svelte-radix": "^1.1.0", + "svelte-sonner": "^0.3.27", "tailwind-merge": "^2.5.2", "tailwind-variants": "^0.2.1", "trpc-svelte-query-adapter": "^2.3.14", diff --git a/src/lib/client/index.ts b/src/lib/client/index.ts index a038bbb..4ccd712 100644 --- a/src/lib/client/index.ts +++ b/src/lib/client/index.ts @@ -5,30 +5,21 @@ export * from "./errors"; import { InvalidKeyError } from "./errors"; import { getContext, setContext } from "svelte"; -const private_key_usages: KeyUsage[] = [ - "decrypt", - "unwrapKey", -]; +const private_key_usages: KeyUsage[] = ["decrypt", "unwrapKey"]; -const public_key_usages: KeyUsage[] = [ - "wrapKey", - "encrypt", -]; +const public_key_usages: KeyUsage[] = ["wrapKey", "encrypt"]; -const key_usages = [ - ...private_key_usages, - ...public_key_usages -]; +const key_usages = [...private_key_usages, ...public_key_usages]; const runtime_client_context_key = "$$_theAmalgamation"; export const setRuntimeClientContext = (client: TheAmalgamation) => { - setContext(runtime_client_context_key, client); -} + setContext(runtime_client_context_key, client); +}; export const getRuntimeClientContext = () => { - return getContext(runtime_client_context_key) as TheAmalgamation; -} + return getContext(runtime_client_context_key) as TheAmalgamation; +}; /** * Behold, the amalgamation, the client that does literally everything on the client. @@ -38,32 +29,38 @@ export class TheAmalgamation { constructor() {} - public initialise_from_localstorage = async () => { - this.key_pairs = []; - console.info("Initialising Runtime Client from Localhost"); - - for (let i = 0; i < window.localStorage.length; i++) { - const item_name = window.localStorage.key(i)!; - if (item_name.startsWith("cached_key-")) { - const cached_key_string = window.localStorage.getItem(item_name)!; - const parsed_cached_key = JSON.parse(cached_key_string); - - const kid = item_name.replace("cached_key-", ""); - - if ( - "publicKey" in parsed_cached_key && typeof parsed_cached_key.publicKey === "object" && - "privateKey" in parsed_cached_key && typeof parsed_cached_key.privateKey === "object" - ) { - const decoded_key = await this.decode_key_pair(parsed_cached_key.publicKey, parsed_cached_key.privateKey); - - this.add_key_pair_to_client(kid, decoded_key); - console.info(`Imported Cached key of KID ${kid}`); - } else { - console.warn(`Cached key of KID ${kid} is invalid`); - } - } - } - } + public initialise_from_localstorage = async () => { + this.key_pairs = []; + console.info("Initialising Runtime Client from Localhost"); + + for (let i = 0; i < window.localStorage.length; i++) { + const item_name = window.localStorage.key(i)!; + if (item_name.startsWith("cached_key-")) { + const cached_key_string = + window.localStorage.getItem(item_name)!; + const parsed_cached_key = JSON.parse(cached_key_string); + + const kid = item_name.replace("cached_key-", ""); + + if ( + "publicKey" in parsed_cached_key && + typeof parsed_cached_key.publicKey === "object" && + "privateKey" in parsed_cached_key && + typeof parsed_cached_key.privateKey === "object" + ) { + const decoded_key = await this.decode_key_pair( + parsed_cached_key.publicKey, + parsed_cached_key.privateKey, + ); + + this.add_key_pair_to_client(kid, decoded_key); + console.info(`Imported Cached key of KID ${kid}`); + } else { + console.warn(`Cached key of KID ${kid} is invalid`); + } + } + } + }; private get_key = (kid: string): JailBirdKey | undefined => { return this.key_pairs.find((val) => val.kid === kid); @@ -75,12 +72,15 @@ export class TheAmalgamation { key, }); - window.localStorage.setItem(`cached_key-${kid}`, JSON.stringify(await this.encode_key_pair(key))); - }; + window.localStorage.setItem( + `cached_key-${kid}`, + JSON.stringify(await this.encode_key_pair(key)), + ); + }; - private encode_key_pair = async (key: CryptoKeyPair) => { - return { - publicKey: await window.crypto.subtle.exportKey( + private encode_key_pair = async (key: CryptoKeyPair) => { + return { + publicKey: await window.crypto.subtle.exportKey( "jwk", key.publicKey, ), @@ -88,38 +88,40 @@ export class TheAmalgamation { "jwk", key.privateKey, ), - } - } - - private decode_key_pair = async (public_key: object, private_key: object): Promise => { - try { - return { - privateKey: await window.crypto.subtle.importKey( - "jwk", - private_key, - { name: "ECDSA", namedCurve: "P-521" }, - true, - private_key_usages, - ), - publicKey: await window.crypto.subtle.importKey( - "jwk", - public_key, - { name: "ECDSA", namedCurve: "P-521" }, - true, - public_key_usages, - ), - } - } catch (e) { - if (e instanceof TypeError || e instanceof SyntaxError) { - // Invalid Keys - throw new InvalidKeyError(); - } else { - // Invalid Use of key usages - throw new InvalidKeyError() - } - } - - } + }; + }; + + private decode_key_pair = async ( + public_key: object, + private_key: object, + ): Promise => { + try { + return { + privateKey: await window.crypto.subtle.importKey( + "jwk", + private_key, + { name: "ECDSA", namedCurve: "P-521" }, + true, + private_key_usages, + ), + publicKey: await window.crypto.subtle.importKey( + "jwk", + public_key, + { name: "ECDSA", namedCurve: "P-521" }, + true, + public_key_usages, + ), + }; + } catch (e) { + if (e instanceof TypeError || e instanceof SyntaxError) { + // Invalid Keys + throw new InvalidKeyError(); + } else { + // Invalid Use of key usages + throw new InvalidKeyError(); + } + } + }; public export_key_for_download = async ( kid: string, @@ -129,7 +131,7 @@ export class TheAmalgamation { return JSON.stringify({ kid, - ...this.encode_key_pair(key.key) + ...this.encode_key_pair(key.key), }); }; @@ -156,8 +158,11 @@ export class TheAmalgamation { const public_key = deserialised_key.publicKey as JsonWebKey; const private_key = deserialised_key.privateKey as JsonWebKey; - const decoded_key = await this.decode_key_pair(public_key, private_key); - if (!decoded_key) throw new InvalidKeyError(); + const decoded_key = await this.decode_key_pair( + public_key, + private_key, + ); + if (!decoded_key) throw new InvalidKeyError(); this.add_key_pair_to_client(kid, decoded_key); } else { diff --git a/src/lib/components/account/account-header-component.svelte b/src/lib/components/account/account-header-component.svelte index 11e633f..4e090ae 100644 --- a/src/lib/components/account/account-header-component.svelte +++ b/src/lib/components/account/account-header-component.svelte @@ -8,7 +8,7 @@ interface Props { user: UserType | null; - avatar_size: "default" | "smol"; + avatar_size?: "default" | "smol"; } const { user = null, avatar_size = "default" }: Props = $props(); diff --git a/src/lib/components/headers/index.ts b/src/lib/components/headers/index.ts index 105e4cf..ad4d39e 100644 --- a/src/lib/components/headers/index.ts +++ b/src/lib/components/headers/index.ts @@ -1,4 +1,4 @@ -import Icons from "../../icons"; +import * as Icons from "../../icons"; export const APPLICATION_NAME = "Jail Bird"; export const APPLICATION_ICON = Icons.Lock; diff --git a/src/lib/components/ui/sonner/index.ts b/src/lib/components/ui/sonner/index.ts new file mode 100644 index 0000000..1ad9f4a --- /dev/null +++ b/src/lib/components/ui/sonner/index.ts @@ -0,0 +1 @@ +export { default as Toaster } from "./sonner.svelte"; diff --git a/src/lib/components/ui/sonner/sonner.svelte b/src/lib/components/ui/sonner/sonner.svelte new file mode 100644 index 0000000..8373ff2 --- /dev/null +++ b/src/lib/components/ui/sonner/sonner.svelte @@ -0,0 +1,25 @@ + + + diff --git a/src/lib/icons/AnimatedLoading.svelte b/src/lib/icons/AnimatedLoading.svelte new file mode 100644 index 0000000..50f7708 --- /dev/null +++ b/src/lib/icons/AnimatedLoading.svelte @@ -0,0 +1,13 @@ + + + diff --git a/src/lib/icons/index.ts b/src/lib/icons/index.ts index 9752d13..9e32030 100644 --- a/src/lib/icons/index.ts +++ b/src/lib/icons/index.ts @@ -1,8 +1,7 @@ import Lock from "./lock.svelte"; +import AnimatedLoading from "./AnimatedLoading.svelte"; -export default { - Lock, -}; +export { Lock, AnimatedLoading }; export interface IconProps { size?: string; diff --git a/src/lib/server/db.ts b/src/lib/server/db.ts index eb13d4b..754695b 100644 --- a/src/lib/server/db.ts +++ b/src/lib/server/db.ts @@ -15,7 +15,7 @@ sqlite.exec("PRAGMA journal_mode = WAL;"); sqlite.exec("PRAGMA foreign_keys = on;"); export const DB = drizzle(sqlite, { - logger: process.env.NODE_ENV == "development", + logger: process.env.NODE_ENV == "development" && ["1", "true"].includes(process.env.LOGGER_ENABLED ?? ""), }); /** diff --git a/src/lib/server/trpc/context.ts b/src/lib/server/trpc/context.ts index ecce27b..66c52c5 100644 --- a/src/lib/server/trpc/context.ts +++ b/src/lib/server/trpc/context.ts @@ -1,8 +1,12 @@ import type { RequestEvent } from "@sveltejs/kit"; export const createContext = async (event: RequestEvent) => { + const { user, session } = event.locals; + return { event, + user, + session, }; }; diff --git a/src/lib/server/trpc/middleware.ts b/src/lib/server/trpc/middleware.ts new file mode 100644 index 0000000..dc9b1a4 --- /dev/null +++ b/src/lib/server/trpc/middleware.ts @@ -0,0 +1,23 @@ +import { experimental_standaloneMiddleware, TRPCError } from "@trpc/server"; + +import type { Context } from "./context"; + +export const authMiddleware = experimental_standaloneMiddleware<{ + ctx: Context; +}>().create(async (opts) => { + const { user, session } = opts.ctx; + + if (!user || !session) { + throw new TRPCError({ + code: "UNAUTHORIZED", + }); + } else { + return opts.next({ + ctx: { + event: opts.ctx.event, + user, + session, + }, + }); + } +}); diff --git a/src/lib/server/trpc/router/auth.ts b/src/lib/server/trpc/router/auth.ts index e491103..29b909b 100644 --- a/src/lib/server/trpc/router/auth.ts +++ b/src/lib/server/trpc/router/auth.ts @@ -25,7 +25,7 @@ const checkAvailability = async (username: string) => { export const authRouter = trpcInstance.router({ // #region Check Username - check_username_availability: trpcInstance.procedure + checkUsernameAvailability: trpcInstance.procedure .input(USERNAME_SCHEMA) .query(async (opts) => { return { @@ -34,7 +34,7 @@ export const authRouter = trpcInstance.router({ }), // #endregion // #region Sign Up - sign_up: trpcInstance.procedure + signUp: trpcInstance.procedure .input( z.object({ username: USERNAME_SCHEMA, @@ -83,7 +83,7 @@ export const authRouter = trpcInstance.router({ }), // #endregion // #region Log In - log_in: trpcInstance.procedure + logIn: trpcInstance.procedure .input( z.object({ username: USERNAME_SCHEMA, diff --git a/src/lib/server/trpc/router/greeter.ts b/src/lib/server/trpc/router/greeter.ts index df29684..eeab8a0 100644 --- a/src/lib/server/trpc/router/greeter.ts +++ b/src/lib/server/trpc/router/greeter.ts @@ -13,7 +13,7 @@ export const greeterRouter = trpcInstance.router({ return `Hello ${opts.input.name}`; }), - odd_or_even: trpcInstance.procedure + oddOrEven: trpcInstance.procedure .input( z.object({ num: z.number(), diff --git a/src/lib/server/trpc/router/index.ts b/src/lib/server/trpc/router/index.ts index 2f19c58..50b2fc7 100644 --- a/src/lib/server/trpc/router/index.ts +++ b/src/lib/server/trpc/router/index.ts @@ -1,13 +1,22 @@ +import type { RequestEvent } from "@sveltejs/kit"; + import { trpcInstance } from "./init"; import { authRouter } from "./auth"; import { greeterRouter } from "./greeter"; +import { userRouter } from "./user"; +import { createContext } from "../context"; export const router = trpcInstance.router({ greeter: greeterRouter, auth: authRouter, + user: userRouter, }); -export const createCaller = trpcInstance.createCallerFactory(router); - export type Router = typeof router; + +const factory = trpcInstance.createCallerFactory(router); + +export const createCaller = async (event: RequestEvent) => { + return factory(await createContext(event)); +}; diff --git a/src/lib/server/trpc/router/user.ts b/src/lib/server/trpc/router/user.ts new file mode 100644 index 0000000..31c5972 --- /dev/null +++ b/src/lib/server/trpc/router/user.ts @@ -0,0 +1,15 @@ +// import z from "zod"; +import { trpcInstance } from "./init"; +import { authMiddleware } from "../middleware"; + +export const userRouter = trpcInstance.router({ + getUploadedFiles: trpcInstance.procedure + .use(authMiddleware) + .query(async () => { + await new Promise((res) => setTimeout(res, 1000)); + + return { + uploadedFiles: Math.floor(Math.random() * 100), + }; + }), +}); diff --git a/src/routes/(app)/app/+page.svelte b/src/routes/(app)/app/+page.svelte index 4b80ec6..1408797 100644 --- a/src/routes/(app)/app/+page.svelte +++ b/src/routes/(app)/app/+page.svelte @@ -1,6 +1,6 @@
-

Welcome to SvelteKit

+

Welcome to SvelteKit

-

- Visit kit.svelte.dev - to read the documentation -

+

+ Visit kit.svelte.dev + to read the documentation +

- - -
\ No newline at end of file + + + diff --git a/src/routes/(app)/app/account/+page.svelte b/src/routes/(app)/app/account/+page.svelte index 555baf3..1f654d1 100644 --- a/src/routes/(app)/app/account/+page.svelte +++ b/src/routes/(app)/app/account/+page.svelte @@ -1,28 +1,22 @@ -{#snippet account_stats()} -
- - - Uploaded Files - - {Math.floor(Math.random() * 100)} - - - -
-{/snippet} + const uploadedFileCount = data.uploadedFileCount(); +
diff --git a/src/routes/(app)/app/account/+page.ts b/src/routes/(app)/app/account/+page.ts new file mode 100644 index 0000000..42328df --- /dev/null +++ b/src/routes/(app)/app/account/+page.ts @@ -0,0 +1,14 @@ +import { trpc } from "$lib/trpc/client"; +import type { PageLoad } from "./$types"; + +export const load = (async (event) => { + const { queryClient } = await event.parent(); + const client = trpc(event, queryClient); + + return { + uploadedFileCount: + await client.user.getUploadedFiles.createServerQuery(undefined, { + ssr: false + }), + }; +}) satisfies PageLoad; diff --git a/src/routes/(auth)/auth/login/+page.svelte b/src/routes/(auth)/auth/login/+page.svelte index 85e8244..a17f5b0 100644 --- a/src/routes/(auth)/auth/login/+page.svelte +++ b/src/routes/(auth)/auth/login/+page.svelte @@ -33,7 +33,7 @@ } }); - const log_in_mutation = rpc.auth.log_in.createMutation({ + const log_in_mutation = rpc.auth.logIn.createMutation({ onSuccess: () => { goto("/app"); }, @@ -51,9 +51,9 @@ }; /** - * Sign Up with Github Button Handler + * Log In with Github Button Handler */ - const github_signup_onclick = (ev: MouseEvent) => { + const githubLoginOnclick = (ev: MouseEvent) => { ev.stopPropagation(); ev.preventDefault(); @@ -174,7 +174,7 @@