From 26fa74ca79ef4cf6a1d6ab42bf778426152cc23d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sun, 18 May 2025 19:39:39 +0200 Subject: [PATCH 1/9] Change project structure --- src/routes/+page.svelte | 69 +---------------------- src/routes/scanner/+page.svelte | 68 ++++++++++++++++++++++ src/routes/{ => scanner}/QRScanner.svelte | 0 src/routes/setup/+page.svelte | 1 + 4 files changed, 70 insertions(+), 68 deletions(-) create mode 100644 src/routes/scanner/+page.svelte rename src/routes/{ => scanner}/QRScanner.svelte (100%) create mode 100644 src/routes/setup/+page.svelte diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 4d9a80f..6d694da 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,68 +1 @@ - - - - -

CODE: {scanned_id}

- -

Name: {scan_data.name} {scan_data.surname}

-

State: {ticket_state}

-

Event: {scan_data.event_name}

+

hello world

\ No newline at end of file diff --git a/src/routes/scanner/+page.svelte b/src/routes/scanner/+page.svelte new file mode 100644 index 0000000..4d9a80f --- /dev/null +++ b/src/routes/scanner/+page.svelte @@ -0,0 +1,68 @@ + + + + +

CODE: {scanned_id}

+ +

Name: {scan_data.name} {scan_data.surname}

+

State: {ticket_state}

+

Event: {scan_data.event_name}

diff --git a/src/routes/QRScanner.svelte b/src/routes/scanner/QRScanner.svelte similarity index 100% rename from src/routes/QRScanner.svelte rename to src/routes/scanner/QRScanner.svelte diff --git a/src/routes/setup/+page.svelte b/src/routes/setup/+page.svelte new file mode 100644 index 0000000..72a7987 --- /dev/null +++ b/src/routes/setup/+page.svelte @@ -0,0 +1 @@ +

setup

\ No newline at end of file -- 2.49.1 From 72713ff5d7f8ade7781c7a29fa4a5f8d7b7089e4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sun, 18 May 2025 19:39:50 +0200 Subject: [PATCH 2/9] Add supabase --- package-lock.json | 148 ++++++++++++++++++++++++++++++++++++++++++++++ package.json | 1 + 2 files changed, 149 insertions(+) diff --git a/package-lock.json b/package-lock.json index bb19d52..1c27001 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,6 +8,7 @@ "name": "esn-code-scanner", "version": "0.0.1", "dependencies": { + "@supabase/supabase-js": "^2.49.4", "@sveltejs/adapter-node": "^5.2.12" }, "devDependencies": { @@ -867,6 +868,80 @@ "win32" ] }, + "node_modules/@supabase/auth-js": { + "version": "2.69.1", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", + "integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/functions-js": { + "version": "2.4.4", + "resolved": "https://registry.npmjs.org/@supabase/functions-js/-/functions-js-2.4.4.tgz", + "integrity": "sha512-WL2p6r4AXNGwop7iwvul2BvOtuJ1YQy8EbOd0dhG1oN1q8el/BIRSFCFnWAMM/vJJlHWLi4ad22sKbKr9mvjoA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/node-fetch": { + "version": "2.6.15", + "resolved": "https://registry.npmjs.org/@supabase/node-fetch/-/node-fetch-2.6.15.tgz", + "integrity": "sha512-1ibVeYUacxWYi9i0cf5efil6adJ9WRyZBLivgjs+AUpewx1F3xPi7gLgaASI2SmIQxPoCEjAsLAzKPgMJVgOUQ==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + } + }, + "node_modules/@supabase/postgrest-js": { + "version": "1.19.4", + "resolved": "https://registry.npmjs.org/@supabase/postgrest-js/-/postgrest-js-1.19.4.tgz", + "integrity": "sha512-O4soKqKtZIW3olqmbXXbKugUtByD2jPa8kL2m2c1oozAO11uCcGrRhkZL0kVxjBLrXHE0mdSkFsMj7jDSfyNpw==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/realtime-js": { + "version": "2.11.2", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", + "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14", + "@types/phoenix": "^1.5.4", + "@types/ws": "^8.5.10", + "ws": "^8.18.0" + } + }, + "node_modules/@supabase/storage-js": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/@supabase/storage-js/-/storage-js-2.7.1.tgz", + "integrity": "sha512-asYHcyDR1fKqrMpytAS1zjyEfvxuOIp1CIXX7ji4lHHcJKqyk+sLl/Vxgm4sN6u8zvuUtae9e4kDxQP2qrwWBA==", + "license": "MIT", + "dependencies": { + "@supabase/node-fetch": "^2.6.14" + } + }, + "node_modules/@supabase/supabase-js": { + "version": "2.49.4", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.4.tgz", + "integrity": "sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==", + "license": "MIT", + "dependencies": { + "@supabase/auth-js": "2.69.1", + "@supabase/functions-js": "2.4.4", + "@supabase/node-fetch": "2.6.15", + "@supabase/postgrest-js": "1.19.4", + "@supabase/realtime-js": "2.11.2", + "@supabase/storage-js": "2.7.1" + } + }, "node_modules/@sveltejs/acorn-typescript": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/@sveltejs/acorn-typescript/-/acorn-typescript-1.0.5.tgz", @@ -1265,12 +1340,36 @@ "integrity": "sha512-w28IoSUCJpidD/TGviZwwMJckNESJZXFu7NBZ5YJ4mEUnNraUn9Pm8HSZm/jDF1pDWYKspWE7oVphigUPRakIQ==", "license": "MIT" }, + "node_modules/@types/node": { + "version": "22.15.18", + "resolved": "https://registry.npmjs.org/@types/node/-/node-22.15.18.tgz", + "integrity": "sha512-v1DKRfUdyW+jJhZNEI1PYy29S2YRxMV5AOO/x/SjKmW0acCIOqmbj6Haf9eHAhsPmrhlHSxEhv/1WszcLWV4cg==", + "license": "MIT", + "dependencies": { + "undici-types": "~6.21.0" + } + }, + "node_modules/@types/phoenix": { + "version": "1.6.6", + "resolved": "https://registry.npmjs.org/@types/phoenix/-/phoenix-1.6.6.tgz", + "integrity": "sha512-PIzZZlEppgrpoT2QgbnDU+MMzuR6BbCjllj0bM70lWoejMeNJAxCchxnv7J3XFkI8MpygtRpzXrIlmWUBclP5A==", + "license": "MIT" + }, "node_modules/@types/resolve": { "version": "1.20.2", "resolved": "https://registry.npmjs.org/@types/resolve/-/resolve-1.20.2.tgz", "integrity": "sha512-60BCwRFOZCQhDncwQdxxeOEEkbc5dIMccYLwbxsS4TUNeVECQ/pBJ0j09mrHOl/JJvpRPGwO9SvE4nR2Nb/a4Q==", "license": "MIT" }, + "node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/acorn": { "version": "8.14.1", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.14.1.tgz", @@ -2346,6 +2445,12 @@ "node": ">=6" } }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, "node_modules/typescript": { "version": "5.8.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.8.3.tgz", @@ -2360,6 +2465,12 @@ "node": ">=14.17" } }, + "node_modules/undici-types": { + "version": "6.21.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", + "integrity": "sha512-iwDZqg0QAGrg9Rav5H4n0M64c3mkR59cJ6wQp+7C4nI0gsmExaedaYLNO44eT4AtBBwjbTiGPMlt2Md0T9H9JQ==", + "license": "MIT" + }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2459,6 +2570,43 @@ } } }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/yallist": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", diff --git a/package.json b/package.json index 5094ead..45eb416 100644 --- a/package.json +++ b/package.json @@ -29,6 +29,7 @@ "vite": "^6.2.6" }, "dependencies": { + "@supabase/supabase-js": "^2.49.4", "@sveltejs/adapter-node": "^5.2.12" } } -- 2.49.1 From 51dbfcc1bbcfbb57ec9476dcc6c9236cc1b8c007 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Fri, 30 May 2025 09:31:21 +0200 Subject: [PATCH 3/9] Inital supabase client set up --- src/routes/+layout.ts | 32 ++++++++++++++++++++++++++++++++ src/routes/+page.svelte | 17 ++++++++++++++++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 src/routes/+layout.ts diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts new file mode 100644 index 0000000..c173c2e --- /dev/null +++ b/src/routes/+layout.ts @@ -0,0 +1,32 @@ +import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' +import type { LayoutLoad } from './$types' +import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' + +export const load: LayoutLoad = async ({ fetch, data, depends }) => { + depends('supabase:auth') + const supabase = isBrowser() + ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { + fetch, + }, + }) + : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + global: { + fetch, + }, + cookies: { + getAll() { + return data?.cookies ?? [] + }, + }, + }) + /** + * It's fine to use `getSession` here, because on the client, `getSession` is + * safe, and on the server, it reads `session` from the `LayoutData`, which + * safely checked the session using `safeGetSession`. + */ + const { + data: { session }, + } = await supabase.auth.getSession() + return { supabase, session } +} \ No newline at end of file diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 6d694da..7642608 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1 +1,16 @@ -

hello world

\ No newline at end of file + + + +

hello world

-- 2.49.1 From 9c94f9c717bc70697f91b8e68edc5c7c3e8e236f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Thu, 19 Jun 2025 19:42:08 +0200 Subject: [PATCH 4/9] Slightly working --- src/app.d.ts | 27 ++++---- src/hooks.server.ts | 53 +++++++++++++++ src/lib/types.ts | 31 +++++++++ src/routes/+layout.server.ts | 10 +++ src/routes/+layout.svelte | 1 + src/routes/+layout.ts | 4 +- src/routes/+page.svelte | 10 --- src/routes/login/+page.svelte | 0 src/routes/private/scanner/+page.svelte | 42 ++++++++++++ .../{ => private}/scanner/QRScanner.svelte | 0 .../private/scanner/TicketDisplay.svelte | 8 +++ src/routes/scanner/+page.svelte | 68 ------------------- 12 files changed, 162 insertions(+), 92 deletions(-) create mode 100644 src/hooks.server.ts create mode 100644 src/lib/types.ts create mode 100644 src/routes/+layout.server.ts create mode 100644 src/routes/login/+page.svelte create mode 100644 src/routes/private/scanner/+page.svelte rename src/routes/{ => private}/scanner/QRScanner.svelte (100%) create mode 100644 src/routes/private/scanner/TicketDisplay.svelte delete mode 100644 src/routes/scanner/+page.svelte diff --git a/src/app.d.ts b/src/app.d.ts index da08e6d..bce918f 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,13 +1,16 @@ -// See https://svelte.dev/docs/kit/types#app.d.ts -// for information about these interfaces +// src/app.d.ts +import { SupabaseClient, Session } from '@supabase/supabase-js' declare global { - namespace App { - // interface Error {} - // interface Locals {} - // interface PageData {} - // interface PageState {} - // interface Platform {} - } -} - -export {}; + namespace App { + interface Locals { + supabase: SupabaseClient + safeGetSession(): Promise<{ session: Session | null; user: User | null }> + } + interface PageData { + session: Session | null + user: User | null + } + // interface Error {} + // interface Platform {} + } +} \ No newline at end of file diff --git a/src/hooks.server.ts b/src/hooks.server.ts new file mode 100644 index 0000000..9a89766 --- /dev/null +++ b/src/hooks.server.ts @@ -0,0 +1,53 @@ +// src/hooks.server.ts +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' +import { createServerClient } from '@supabase/ssr' +import type { Handle } from '@sveltejs/kit' + +export const handle: Handle = async ({ event, resolve }) => { + event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { + cookies: { + getAll: () => event.cookies.getAll(), + /** + * SvelteKit's cookies API requires `path` to be explicitly set in + * the cookie options. Setting `path` to `/` replicates previous/ + * standard behavior. + */ + setAll: (cookiesToSet) => { + cookiesToSet.forEach(({ name, value, options }) => { + event.cookies.set(name, value, { ...options, path: '/' }) + }) + }, + }, + }) + + /** + * Unlike `supabase.auth.getSession()`, which returns the session _without_ + * validating the JWT, this function also calls `getUser()` to validate the + * JWT before returning the session. + */ + event.locals.safeGetSession = async () => { + const { + data: { session }, + } = await event.locals.supabase.auth.getSession() + if (!session) { + return { session: null, user: null } + } + + const { + data: { user }, + error, + } = await event.locals.supabase.auth.getUser() + if (error) { + // JWT validation has failed + return { session: null, user: null } + } + + return { session, user } + } + + return resolve(event, { + filterSerializedResponseHeaders(name) { + return name === 'content-range' || name === 'x-supabase-api-version' + }, + }) +} \ No newline at end of file diff --git a/src/lib/types.ts b/src/lib/types.ts new file mode 100644 index 0000000..e9558fb --- /dev/null +++ b/src/lib/types.ts @@ -0,0 +1,31 @@ +export enum ScanState { + scanning, + scan_successful, + scan_failed +} + +export type TicketData = { + id: string; + name: string; + surname: string; + email: string; + event: string; + created_at: string; + created_by: string | null; + scanned: boolean; + scanned_at: string | null; + scanned_by: string | null; +}; + +export const defaultTicketData: TicketData = { + id: '', + name: '', + surname: '', + email: '', + event: '', + created_at: new Date().toISOString(), + created_by: null, + scanned: false, + scanned_at: null, + scanned_by: null, +}; \ No newline at end of file diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts new file mode 100644 index 0000000..55e62fe --- /dev/null +++ b/src/routes/+layout.server.ts @@ -0,0 +1,10 @@ +// src/routes/+layout.server.ts +import type { LayoutServerLoad } from './$types' +export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => { + const { session, user } = await safeGetSession() + return { + session, + user, + cookies: cookies.getAll(), + } +} \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b93e9ba..b8a1c4d 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -2,6 +2,7 @@ import '../app.css'; let { children } = $props(); + {@render children()} diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index c173c2e..768b716 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -8,7 +8,7 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { global: { fetch, - }, + } }) : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { global: { @@ -16,7 +16,7 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { }, cookies: { getAll() { - return data?.cookies ?? [] + return data.cookies ?? [] }, }, }) diff --git a/src/routes/+page.svelte b/src/routes/+page.svelte index 7642608..f229fc2 100644 --- a/src/routes/+page.svelte +++ b/src/routes/+page.svelte @@ -1,15 +1,5 @@ diff --git a/src/routes/login/+page.svelte b/src/routes/login/+page.svelte new file mode 100644 index 0000000..e69de29 diff --git a/src/routes/private/scanner/+page.svelte b/src/routes/private/scanner/+page.svelte new file mode 100644 index 0000000..a3843f9 --- /dev/null +++ b/src/routes/private/scanner/+page.svelte @@ -0,0 +1,42 @@ + + + + +{#if scan_state === ScanState.scan_successful} + +{/if} + +{#if scan_state === ScanState.scan_failed} +

Scan failed. Please try again.

+{/if} + +{#if scan_state === ScanState.scanning} +

Fetching data...

+{/if} diff --git a/src/routes/scanner/QRScanner.svelte b/src/routes/private/scanner/QRScanner.svelte similarity index 100% rename from src/routes/scanner/QRScanner.svelte rename to src/routes/private/scanner/QRScanner.svelte diff --git a/src/routes/private/scanner/TicketDisplay.svelte b/src/routes/private/scanner/TicketDisplay.svelte new file mode 100644 index 0000000..6e76511 --- /dev/null +++ b/src/routes/private/scanner/TicketDisplay.svelte @@ -0,0 +1,8 @@ + + +

{ticket_data.name}

+

{ticket_data.surname}

diff --git a/src/routes/scanner/+page.svelte b/src/routes/scanner/+page.svelte deleted file mode 100644 index 4d9a80f..0000000 --- a/src/routes/scanner/+page.svelte +++ /dev/null @@ -1,68 +0,0 @@ - - - - -

CODE: {scanned_id}

- -

Name: {scan_data.name} {scan_data.surname}

-

State: {ticket_state}

-

Event: {scan_data.event_name}

-- 2.49.1 From 58872bada670a729fdaa7e49f506a0a0734fae5e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Thu, 19 Jun 2025 20:25:36 +0200 Subject: [PATCH 5/9] Working --- package-lock.json | 54 +++++++++++++------ package.json | 3 +- src/app.css | 5 +- src/app.d.ts | 21 +++++--- src/hooks.server.ts | 38 +++++++++++-- src/routes/+layout.server.ts | 5 +- src/routes/+layout.svelte | 19 +++++-- src/routes/+layout.ts | 21 ++++++-- src/routes/auth/+layout.svelte | 11 ++++ src/routes/auth/+page.server.ts | 32 +++++++++++ src/routes/auth/+page.svelte | 12 +++++ src/routes/auth/confirm/+server.ts | 31 +++++++++++ src/routes/auth/error/+page.svelte | 1 + src/routes/private/+layout.server.ts | 5 ++ src/routes/private/home/+page.svelte | 6 +++ src/routes/private/scanner/+page.svelte | 10 +++- src/routes/private/scanner/QRScanner.svelte | 3 +- .../private/scanner/TicketDisplay.svelte | 8 ++- 18 files changed, 237 insertions(+), 48 deletions(-) create mode 100644 src/routes/auth/+layout.svelte create mode 100644 src/routes/auth/+page.server.ts create mode 100644 src/routes/auth/+page.svelte create mode 100644 src/routes/auth/confirm/+server.ts create mode 100644 src/routes/auth/error/+page.svelte create mode 100644 src/routes/private/+layout.server.ts create mode 100644 src/routes/private/home/+page.svelte diff --git a/package-lock.json b/package-lock.json index 1c27001..7989488 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,8 @@ "name": "esn-code-scanner", "version": "0.0.1", "dependencies": { - "@supabase/supabase-js": "^2.49.4", + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.50.0", "@sveltejs/adapter-node": "^5.2.12" }, "devDependencies": { @@ -869,9 +870,9 @@ ] }, "node_modules/@supabase/auth-js": { - "version": "2.69.1", - "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.69.1.tgz", - "integrity": "sha512-FILtt5WjCNzmReeRLq5wRs3iShwmnWgBvxHfqapC/VoljJl+W8hDAyFmf1NVw3zH+ZjZ05AKxiKxVeb0HNWRMQ==", + "version": "2.70.0", + "resolved": "https://registry.npmjs.org/@supabase/auth-js/-/auth-js-2.70.0.tgz", + "integrity": "sha512-BaAK/tOAZFJtzF1sE3gJ2FwTjLf4ky3PSvcvLGEgEmO4BSBkwWKu8l67rLLIBZPDnCyV7Owk2uPyKHa0kj5QGg==", "license": "MIT", "dependencies": { "@supabase/node-fetch": "^2.6.14" @@ -908,15 +909,36 @@ } }, "node_modules/@supabase/realtime-js": { - "version": "2.11.2", - "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.2.tgz", - "integrity": "sha512-u/XeuL2Y0QEhXSoIPZZwR6wMXgB+RQbJzG9VErA3VghVt7uRfSVsjeqd7m5GhX3JR6dM/WRmLbVR8URpDWG4+w==", + "version": "2.11.10", + "resolved": "https://registry.npmjs.org/@supabase/realtime-js/-/realtime-js-2.11.10.tgz", + "integrity": "sha512-SJKVa7EejnuyfImrbzx+HaD9i6T784khuw1zP+MBD7BmJYChegGxYigPzkKX8CK8nGuDntmeSD3fvriaH0EGZA==", "license": "MIT", "dependencies": { - "@supabase/node-fetch": "^2.6.14", - "@types/phoenix": "^1.5.4", - "@types/ws": "^8.5.10", - "ws": "^8.18.0" + "@supabase/node-fetch": "^2.6.13", + "@types/phoenix": "^1.6.6", + "@types/ws": "^8.18.1", + "ws": "^8.18.2" + } + }, + "node_modules/@supabase/ssr": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/@supabase/ssr/-/ssr-0.6.1.tgz", + "integrity": "sha512-QtQgEMvaDzr77Mk3vZ3jWg2/y+D8tExYF7vcJT+wQ8ysuvOeGGjYbZlvj5bHYsj/SpC0bihcisnwPrM4Gp5G4g==", + "license": "MIT", + "dependencies": { + "cookie": "^1.0.1" + }, + "peerDependencies": { + "@supabase/supabase-js": "^2.43.4" + } + }, + "node_modules/@supabase/ssr/node_modules/cookie": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-1.0.2.tgz", + "integrity": "sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==", + "license": "MIT", + "engines": { + "node": ">=18" } }, "node_modules/@supabase/storage-js": { @@ -929,16 +951,16 @@ } }, "node_modules/@supabase/supabase-js": { - "version": "2.49.4", - "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.49.4.tgz", - "integrity": "sha512-jUF0uRUmS8BKt37t01qaZ88H9yV1mbGYnqLeuFWLcdV+x1P4fl0yP9DGtaEhFPZcwSom7u16GkLEH9QJZOqOkw==", + "version": "2.50.0", + "resolved": "https://registry.npmjs.org/@supabase/supabase-js/-/supabase-js-2.50.0.tgz", + "integrity": "sha512-M1Gd5tPaaghYZ9OjeO1iORRqbTWFEz/cF3pPubRnMPzA+A8SiUsXXWDP+DWsASZcjEcVEcVQIAF38i5wrijYOg==", "license": "MIT", "dependencies": { - "@supabase/auth-js": "2.69.1", + "@supabase/auth-js": "2.70.0", "@supabase/functions-js": "2.4.4", "@supabase/node-fetch": "2.6.15", "@supabase/postgrest-js": "1.19.4", - "@supabase/realtime-js": "2.11.2", + "@supabase/realtime-js": "2.11.10", "@supabase/storage-js": "2.7.1" } }, diff --git a/package.json b/package.json index 45eb416..4282d0b 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,8 @@ "vite": "^6.2.6" }, "dependencies": { - "@supabase/supabase-js": "^2.49.4", + "@supabase/ssr": "^0.6.1", + "@supabase/supabase-js": "^2.50.0", "@sveltejs/adapter-node": "^5.2.12" } } diff --git a/src/app.css b/src/app.css index 1c4d2a8..97f54b0 100644 --- a/src/app.css +++ b/src/app.css @@ -1,2 +1,5 @@ @import 'tailwindcss'; -@plugin '@tailwindcss/typography'; + +body { + font-family: "Roboto", sans-serif; +} \ No newline at end of file diff --git a/src/app.d.ts b/src/app.d.ts index bce918f..9a0cf33 100644 --- a/src/app.d.ts +++ b/src/app.d.ts @@ -1,16 +1,21 @@ -// src/app.d.ts -import { SupabaseClient, Session } from '@supabase/supabase-js' +import type { Session, SupabaseClient, User } from '@supabase/supabase-js' +import type { Database } from './database.types.ts' // import generated types + declare global { namespace App { + // interface Error {} interface Locals { - supabase: SupabaseClient - safeGetSession(): Promise<{ session: Session | null; user: User | null }> - } - interface PageData { + supabase: SupabaseClient + safeGetSession: () => Promise<{ session: Session | null; user: User | null }> session: Session | null user: User | null } - // interface Error {} + interface PageData { + session: Session | null + } + // interface PageState {} // interface Platform {} } -} \ No newline at end of file +} + +export {} \ No newline at end of file diff --git a/src/hooks.server.ts b/src/hooks.server.ts index 9a89766..079d393 100644 --- a/src/hooks.server.ts +++ b/src/hooks.server.ts @@ -1,9 +1,15 @@ -// src/hooks.server.ts -import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' import { createServerClient } from '@supabase/ssr' -import type { Handle } from '@sveltejs/kit' +import { type Handle, redirect } from '@sveltejs/kit' +import { sequence } from '@sveltejs/kit/hooks' -export const handle: Handle = async ({ event, resolve }) => { +import { PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY } from '$env/static/public' + +const supabase: Handle = async ({ event, resolve }) => { + /** + * Creates a Supabase client specific to this server request. + * + * The Supabase client gets the Auth token from the request cookies. + */ event.locals.supabase = createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { cookies: { getAll: () => event.cookies.getAll(), @@ -47,7 +53,29 @@ export const handle: Handle = async ({ event, resolve }) => { return resolve(event, { filterSerializedResponseHeaders(name) { + /** + * Supabase libraries use the `content-range` and `x-supabase-api-version` + * headers, so we need to tell SvelteKit to pass it through. + */ return name === 'content-range' || name === 'x-supabase-api-version' }, }) -} \ No newline at end of file +} + +const authGuard: Handle = async ({ event, resolve }) => { + const { session, user } = await event.locals.safeGetSession() + event.locals.session = session + event.locals.user = user + + if (!event.locals.session && event.url.pathname.startsWith('/private')) { + redirect(303, '/auth') + } + + if (event.locals.session && event.url.pathname === '/auth') { + redirect(303, '/private/home') + } + + return resolve(event) +} + +export const handle: Handle = sequence(supabase, authGuard) \ No newline at end of file diff --git a/src/routes/+layout.server.ts b/src/routes/+layout.server.ts index 55e62fe..7543dfa 100644 --- a/src/routes/+layout.server.ts +++ b/src/routes/+layout.server.ts @@ -1,10 +1,9 @@ -// src/routes/+layout.server.ts import type { LayoutServerLoad } from './$types' + export const load: LayoutServerLoad = async ({ locals: { safeGetSession }, cookies }) => { - const { session, user } = await safeGetSession() + const { session } = await safeGetSession() return { session, - user, cookies: cookies.getAll(), } } \ No newline at end of file diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index b8a1c4d..f2ee8ac 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,8 +1,19 @@ - -{@render children()} +{@render children()} \ No newline at end of file diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 768b716..7a40abc 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -1,14 +1,19 @@ +import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' import { PUBLIC_SUPABASE_ANON_KEY, PUBLIC_SUPABASE_URL } from '$env/static/public' import type { LayoutLoad } from './$types' -import { createBrowserClient, createServerClient, isBrowser } from '@supabase/ssr' -export const load: LayoutLoad = async ({ fetch, data, depends }) => { +export const load: LayoutLoad = async ({ data, depends, fetch }) => { + /** + * Declare a dependency so the layout can be invalidated, for example, on + * session refresh. + */ depends('supabase:auth') + const supabase = isBrowser() ? createBrowserClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { global: { fetch, - } + }, }) : createServerClient(PUBLIC_SUPABASE_URL, PUBLIC_SUPABASE_ANON_KEY, { global: { @@ -16,10 +21,11 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { }, cookies: { getAll() { - return data.cookies ?? [] + return data.cookies }, }, }) + /** * It's fine to use `getSession` here, because on the client, `getSession` is * safe, and on the server, it reads `session` from the `LayoutData`, which @@ -28,5 +34,10 @@ export const load: LayoutLoad = async ({ fetch, data, depends }) => { const { data: { session }, } = await supabase.auth.getSession() - return { supabase, session } + + const { + data: { user }, + } = await supabase.auth.getUser() + + return { session, supabase, user } } \ No newline at end of file diff --git a/src/routes/auth/+layout.svelte b/src/routes/auth/+layout.svelte new file mode 100644 index 0000000..042ea05 --- /dev/null +++ b/src/routes/auth/+layout.svelte @@ -0,0 +1,11 @@ + + +
+ +
+ +{@render children()} \ No newline at end of file diff --git a/src/routes/auth/+page.server.ts b/src/routes/auth/+page.server.ts new file mode 100644 index 0000000..57908d2 --- /dev/null +++ b/src/routes/auth/+page.server.ts @@ -0,0 +1,32 @@ +import { redirect } from '@sveltejs/kit' + +import type { Actions } from './$types' + +export const actions: Actions = { + signup: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signUp({ email, password }) + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/') + } + }, + login: async ({ request, locals: { supabase } }) => { + const formData = await request.formData() + const email = formData.get('email') as string + const password = formData.get('password') as string + + const { error } = await supabase.auth.signInWithPassword({ email, password }) + if (error) { + console.error(error) + redirect(303, '/auth/error') + } else { + redirect(303, '/private/home') + } + }, +} \ No newline at end of file diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte new file mode 100644 index 0000000..cded2fe --- /dev/null +++ b/src/routes/auth/+page.svelte @@ -0,0 +1,12 @@ +
+ + + + +
\ No newline at end of file diff --git a/src/routes/auth/confirm/+server.ts b/src/routes/auth/confirm/+server.ts new file mode 100644 index 0000000..4fd0574 --- /dev/null +++ b/src/routes/auth/confirm/+server.ts @@ -0,0 +1,31 @@ +import type { EmailOtpType } from '@supabase/supabase-js' +import { redirect } from '@sveltejs/kit' + +import type { RequestHandler } from './$types' + +export const GET: RequestHandler = async ({ url, locals: { supabase } }) => { + const token_hash = url.searchParams.get('token_hash') + const type = url.searchParams.get('type') as EmailOtpType | null + const next = url.searchParams.get('next') ?? '/' + + /** + * Clean up the redirect URL by deleting the Auth flow parameters. + * + * `next` is preserved for now, because it's needed in the error case. + */ + const redirectTo = new URL(url) + redirectTo.pathname = next + redirectTo.searchParams.delete('token_hash') + redirectTo.searchParams.delete('type') + + if (token_hash && type) { + const { error } = await supabase.auth.verifyOtp({ type, token_hash }) + if (!error) { + redirectTo.searchParams.delete('next') + redirect(303, redirectTo) + } + } + + redirectTo.pathname = '/auth/error' + redirect(303, redirectTo) +} \ No newline at end of file diff --git a/src/routes/auth/error/+page.svelte b/src/routes/auth/error/+page.svelte new file mode 100644 index 0000000..0176f4c --- /dev/null +++ b/src/routes/auth/error/+page.svelte @@ -0,0 +1 @@ +

Login error

\ No newline at end of file diff --git a/src/routes/private/+layout.server.ts b/src/routes/private/+layout.server.ts new file mode 100644 index 0000000..803cddb --- /dev/null +++ b/src/routes/private/+layout.server.ts @@ -0,0 +1,5 @@ +/** + * This file is necessary to ensure protection of all routes in the `private` + * directory. It makes the routes in this directory _dynamic_ routes, which + * send a server request, and thus trigger `hooks.server.ts`. + **/ \ No newline at end of file diff --git a/src/routes/private/home/+page.svelte b/src/routes/private/home/+page.svelte new file mode 100644 index 0000000..36a3c0b --- /dev/null +++ b/src/routes/private/home/+page.svelte @@ -0,0 +1,6 @@ + + + +heyy \ No newline at end of file diff --git a/src/routes/private/scanner/+page.svelte b/src/routes/private/scanner/+page.svelte index a3843f9..b03a903 100644 --- a/src/routes/private/scanner/+page.svelte +++ b/src/routes/private/scanner/+page.svelte @@ -27,6 +27,12 @@ }); + + {#if scan_state === ScanState.scan_successful} @@ -34,9 +40,9 @@ {/if} {#if scan_state === ScanState.scan_failed} -

Scan failed. Please try again.

+

Scan failed. Please try again.

{/if} {#if scan_state === ScanState.scanning} -

Fetching data...

+

Fetching data...

{/if} diff --git a/src/routes/private/scanner/QRScanner.svelte b/src/routes/private/scanner/QRScanner.svelte index e85b5fb..9bb65d4 100644 --- a/src/routes/private/scanner/QRScanner.svelte +++ b/src/routes/private/scanner/QRScanner.svelte @@ -4,8 +4,7 @@ Html5QrcodeScanner, type Html5QrcodeResult, Html5QrcodeScanType, - Html5QrcodeSupportedFormats, - Html5QrcodeScannerState, + Html5QrcodeSupportedFormats } from 'html5-qrcode'; let width: number = 300; diff --git a/src/routes/private/scanner/TicketDisplay.svelte b/src/routes/private/scanner/TicketDisplay.svelte index 6e76511..5d360b1 100644 --- a/src/routes/private/scanner/TicketDisplay.svelte +++ b/src/routes/private/scanner/TicketDisplay.svelte @@ -4,5 +4,11 @@ let { ticket_data }: { ticket_data: TicketData } = $props(); -

{ticket_data.name}

+

{ticket_data.name}

{ticket_data.surname}

+ + \ No newline at end of file -- 2.49.1 From a135c5ac36c0b19d7391c12a315abba9bf2fdb6f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 21 Jun 2025 20:47:22 +0200 Subject: [PATCH 6/9] Working nice, looks like shit --- src/app.css | 6 +---- src/app.html | 8 ++++++ src/routes/auth/+page.server.ts | 11 +++++++- src/routes/auth/+page.svelte | 4 +++ src/routes/private/scanner/+page.svelte | 27 +++++++------------ .../private/scanner/TicketDisplay.svelte | 26 ++++++++++++------ 6 files changed, 50 insertions(+), 32 deletions(-) diff --git a/src/app.css b/src/app.css index 97f54b0..f173aa4 100644 --- a/src/app.css +++ b/src/app.css @@ -1,5 +1 @@ -@import 'tailwindcss'; - -body { - font-family: "Roboto", sans-serif; -} \ No newline at end of file +@import 'tailwindcss'; \ No newline at end of file diff --git a/src/app.html b/src/app.html index 77a5ff5..d2d0706 100644 --- a/src/app.html +++ b/src/app.html @@ -10,3 +10,11 @@
%sveltekit.body%
+ + + \ No newline at end of file diff --git a/src/routes/auth/+page.server.ts b/src/routes/auth/+page.server.ts index 57908d2..39c04e2 100644 --- a/src/routes/auth/+page.server.ts +++ b/src/routes/auth/+page.server.ts @@ -7,8 +7,17 @@ export const actions: Actions = { const formData = await request.formData() const email = formData.get('email') as string const password = formData.get('password') as string + const display_name = formData.get('display_name') as string - const { error } = await supabase.auth.signUp({ email, password }) + const { error } = await supabase.auth.signUp({ + email, + password, + options: { + data: { + display_name: display_name + } + } + }); if (error) { console.error(error) redirect(303, '/auth/error') diff --git a/src/routes/auth/+page.svelte b/src/routes/auth/+page.svelte index cded2fe..3da7d61 100644 --- a/src/routes/auth/+page.svelte +++ b/src/routes/auth/+page.svelte @@ -7,6 +7,10 @@ Password + \ No newline at end of file diff --git a/src/routes/private/scanner/+page.svelte b/src/routes/private/scanner/+page.svelte index b03a903..12c0e9c 100644 --- a/src/routes/private/scanner/+page.svelte +++ b/src/routes/private/scanner/+page.svelte @@ -15,10 +15,17 @@ console.log('New QR code found:', scanned_id); scan_state = ScanState.scanning; - data.supabase.from('qrcodes').select().eq('id', scanned_id).then( response => { + data.supabase + .from('qrcodes') + .select(`*, event ( id, name ), scanned_by ( id, display_name )`) + .eq('id', scanned_id) + .then( response => { if (response.data && response.data.length > 0) { ticket_data = response.data[0]; scan_state = ScanState.scan_successful; + data.supabase.rpc('scan_ticket', { _ticket_id: scanned_id}) + console.log(scanned_id) + console.log(response.data[0]); } else { ticket_data = defaultTicketData; scan_state = ScanState.scan_failed; @@ -27,22 +34,6 @@ }); - - -{#if scan_state === ScanState.scan_successful} - -{/if} - -{#if scan_state === ScanState.scan_failed} -

Scan failed. Please try again.

-{/if} - -{#if scan_state === ScanState.scanning} -

Fetching data...

-{/if} + diff --git a/src/routes/private/scanner/TicketDisplay.svelte b/src/routes/private/scanner/TicketDisplay.svelte index 5d360b1..8e2dc00 100644 --- a/src/routes/private/scanner/TicketDisplay.svelte +++ b/src/routes/private/scanner/TicketDisplay.svelte @@ -1,14 +1,24 @@ -

{ticket_data.name}

-

{ticket_data.surname}

+{#if scan_state === ScanState.scanning} +

Waiting for data...

- \ No newline at end of file +{:else if scan_state === ScanState.scan_failed} +

Scan failed. Please try again.

+ +{:else if scan_state === ScanState.scan_successful} + {#if ticket_data.scanned} +

Ticket already scanned!

+

By {ticket_data.scanned_by} at {ticket_data.scanned_at}

+ {:else} +

Scan successful!

+
    +
  1. {ticket_data.name} {ticket_data.surname}
  2. +
+ {/if} +{/if} \ No newline at end of file -- 2.49.1 From b6d9b8df44dd6427f9a2885e5ebaae7add6cd38c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Roman=20Kr=C4=8Dek?= Date: Sat, 21 Jun 2025 21:35:52 +0200 Subject: [PATCH 7/9] Looking better --- src/lib/types.ts | 20 +++---- src/routes/+layout.svelte | 1 + src/routes/private/+layout.svelte | 15 ++++++ src/routes/private/scanner/+page.svelte | 4 +- src/routes/private/scanner/QRScanner.svelte | 6 ++- .../private/scanner/TicketDisplay.svelte | 54 +++++++++++++------ 6 files changed, 69 insertions(+), 31 deletions(-) create mode 100644 src/routes/private/+layout.svelte diff --git a/src/lib/types.ts b/src/lib/types.ts index e9558fb..36fb4ed 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -5,16 +5,16 @@ export enum ScanState { } export type TicketData = { - id: string; - name: string; - surname: string; - email: string; - event: string; - created_at: string; - created_by: string | null; - scanned: boolean; - scanned_at: string | null; - scanned_by: string | null; + id: string; + name: string; + surname: string; + email: string; + event: { id: string; name: string }; + created_at: string; + created_by: { id: string; display_name: string } | null; + scanned: boolean; + scanned_at: string | null; + scanned_by: { id: string; display_name: string } | null; }; export const defaultTicketData: TicketData = { diff --git a/src/routes/+layout.svelte b/src/routes/+layout.svelte index f2ee8ac..a8a75b4 100644 --- a/src/routes/+layout.svelte +++ b/src/routes/+layout.svelte @@ -1,6 +1,7 @@ + + + +
+ +
diff --git a/src/routes/private/scanner/+page.svelte b/src/routes/private/scanner/+page.svelte index 12c0e9c..3bf1679 100644 --- a/src/routes/private/scanner/+page.svelte +++ b/src/routes/private/scanner/+page.svelte @@ -23,9 +23,7 @@ if (response.data && response.data.length > 0) { ticket_data = response.data[0]; scan_state = ScanState.scan_successful; - data.supabase.rpc('scan_ticket', { _ticket_id: scanned_id}) - console.log(scanned_id) - console.log(response.data[0]); + data.supabase.rpc('scan_ticket', { _ticket_id: scanned_id}).then(); } else { ticket_data = defaultTicketData; scan_state = ScanState.scan_failed; diff --git a/src/routes/private/scanner/QRScanner.svelte b/src/routes/private/scanner/QRScanner.svelte index 9bb65d4..e2f8d73 100644 --- a/src/routes/private/scanner/QRScanner.svelte +++ b/src/routes/private/scanner/QRScanner.svelte @@ -39,7 +39,7 @@ }); -
+