this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

front: client side auth

Clément 6c17ca7d 6196d597

+156 -21
+1
frontend/.env.development
··· 1 + PUBLIC_API_URL=http://localhost:8787
+1
frontend/.env.production
··· 1 + PUBLIC_API_URL=https://api.uiua.online
+3
frontend/.gitignore
··· 28 28 29 29 # Cloudflare Types 30 30 /worker-configuration.d.ts 31 + 32 + !.env.production 33 + !.env.development
+1
frontend/package.json
··· 39 39 "svelte": "^5.48.2", 40 40 "svelte-check": "^4.3.5", 41 41 "tailwindcss": "^4.1.18", 42 + "ts-pattern": "^5.9.0", 42 43 "typescript": "^5.9.3", 43 44 "typescript-eslint": "^8.53.1", 44 45 "vite": "^7.3.1",
+3 -1
frontend/src/app.d.ts
··· 10 10 } 11 11 12 12 // interface Error {} 13 - // interface Locals {} 13 + interface Locals { 14 + session: string; 15 + } 14 16 // interface PageData {} 15 17 // interface PageState {} 16 18 // interface Platform {}
+12 -1
frontend/src/hooks.server.ts
··· 1 1 import type { Handle } from '@sveltejs/kit'; 2 2 import { paraglideMiddleware } from '$lib/paraglide/server'; 3 + import { sequence } from '@sveltejs/kit/hooks'; 3 4 4 5 const handleParaglide: Handle = ({ event, resolve }) => 5 6 paraglideMiddleware(event.request, ({ request, locale }) => { ··· 10 11 }); 11 12 }); 12 13 13 - export const handle: Handle = handleParaglide; 14 + export const handleSession: Handle = async ({ event, resolve }) => { 15 + const session = event.cookies.get('session'); 16 + 17 + if (session) { 18 + event.locals.session = session; 19 + } 20 + 21 + return resolve(event); 22 + }; 23 + 24 + export const handle = sequence(handleParaglide, handleSession);
+50
frontend/src/lib/api/client.ts
··· 1 + import { PUBLIC_API_URL } from '$env/static/public'; 2 + import type { LoginRequest } from './types'; 3 + 4 + class ApiClient { 5 + private baseUrl: string; 6 + 7 + constructor() { 8 + this.baseUrl = PUBLIC_API_URL; 9 + } 10 + 11 + private async request<T>(endpoint: string, options?: RequestInit): Promise<T> { 12 + const url = `${this.baseUrl}${endpoint}`; 13 + 14 + const res = await fetch(url, { 15 + ...options, 16 + credentials: 'include' // Send cookies 17 + }); 18 + 19 + if (!res.ok) { 20 + const error = await res.text(); 21 + throw new Error(error || `HTTP ${res.status}`); 22 + } 23 + 24 + if (res.headers.get('content-type')?.includes('application/json')) { 25 + return res.json(); 26 + } 27 + return res.text() as T; 28 + } 29 + 30 + async login(data: LoginRequest) { 31 + return this.request<string>('/auth/login', { 32 + method: 'POST', 33 + body: JSON.stringify(data), 34 + headers: { 35 + 'Content-Type': 'application/json' 36 + } 37 + }); 38 + } 39 + 40 + async whoami(locals: App.Locals) { 41 + return this.request<string>('/auth/whoami', { 42 + method: 'GET', 43 + headers: { 44 + Authorization: `Bearer ${locals.session}` 45 + } 46 + }); 47 + } 48 + } 49 + 50 + export const client = new ApiClient();
+8
frontend/src/routes/(logged)/+layout.server.ts
··· 1 + import { client } from '$lib/api/client'; 2 + import { redirect } from '@sveltejs/kit'; 3 + import type { LayoutServerLoad } from './$types'; 4 + 5 + export const load: LayoutServerLoad = async ({ locals }) => { 6 + const whoami = await client.whoami(locals).catch(() => redirect(302, '/login')); 7 + return { whoami }; 8 + };
+12
frontend/src/routes/(logged)/dashboard/+page.svelte
··· 1 + <script lang="ts"> 2 + import CodeEditor from '$lib/components/CodeEditor.svelte'; 3 + import { code as pythonCodeSnippet } from '$lib/code_snippet/python'; 4 + import type { PageProps } from './$types'; 5 + 6 + let content = $state(pythonCodeSnippet); 7 + 8 + let { data }: PageProps = $props(); 9 + </script> 10 + 11 + <p>Welcome, {data.whoami}!</p> 12 + <CodeEditor class="" language="python" bind:value={content} />
-8
frontend/src/routes/+page.svelte
··· 1 - <script lang="ts"> 2 - import CodeEditor from './CodeEditor.svelte'; 3 - import { code as pythonCodeSnippet } from '$lib/code_snippet/python'; 4 - 5 - let content = $state(pythonCodeSnippet); 6 - </script> 7 - 8 - <CodeEditor class="h-80" language="python" bind:value={content} />
frontend/src/routes/CodeEditor.svelte frontend/src/lib/components/CodeEditor.svelte
+31
frontend/src/routes/login/+page.server.ts
··· 1 + import type { Actions } from './$types'; 2 + import { client } from '$lib/api/client'; 3 + import { fail, redirect } from '@sveltejs/kit'; 4 + 5 + export const actions: Actions = { 6 + default: async ({ request, cookies }) => { 7 + const params = await request.formData(); 8 + const email = params.get('email')?.toString(); 9 + const password = params.get('password')?.toString(); 10 + 11 + if (!email || !password) { 12 + return fail(400, { error: 'One field or more is missing' }); 13 + } 14 + 15 + let token: string; 16 + try { 17 + token = await client.login({ email, password }); 18 + } catch (err) { 19 + return fail(409, { error: err instanceof Error ? err.message : 'Login failed' }); 20 + } 21 + 22 + cookies.set('session', token, { 23 + path: '/', 24 + httpOnly: true, 25 + secure: true, 26 + sameSite: 'lax' 27 + }); 28 + 29 + return redirect(302, '/dashboard'); 30 + } 31 + };
+15
frontend/src/routes/login/+page.svelte
··· 1 + <script lang="ts"> 2 + import { enhance } from '$app/forms'; 3 + 4 + const id = $props.id(); 5 + let { form } = $props(); 6 + </script> 7 + 8 + <form method="post" use:enhance> 9 + <input id={`${id}-email`} name="email" type="email" placeholder="Email" required /> 10 + <input id={`${id}-password`} name="password" type="password" placeholder="Password" required /> 11 + <button type="submit">Login</button> 12 + {#if form?.error} 13 + <p class="error">{form.error}</p> 14 + {/if} 15 + </form>
+11 -11
frontend/src/routes/page.svelte.spec.ts
··· 1 - import { page } from 'vitest/browser'; 2 - import { describe, expect, it } from 'vitest'; 3 - import { render } from 'vitest-browser-svelte'; 4 - import Page from './+page.svelte'; 1 + // import { page } from 'vitest/browser'; 2 + // import { describe, expect, it } from 'vitest'; 3 + // import { render } from 'vitest-browser-svelte'; 4 + // import Page from './+page.svelte'; 5 5 6 - describe('/+page.svelte', () => { 7 - it('should render h1', async () => { 8 - render(Page); 6 + // describe('/+page.svelte', () => { 7 + // it('should render h1', async () => { 8 + // render(Page); 9 9 10 - const heading = page.getByRole('heading', { level: 1 }); 11 - await expect.element(heading).toBeInTheDocument(); 12 - }); 13 - }); 10 + // const heading = page.getByRole('heading', { level: 1 }); 11 + // await expect.element(heading).toBeInTheDocument(); 12 + // }); 13 + // });
+8
pnpm-lock.yaml
··· 74 74 tailwindcss: 75 75 specifier: ^4.1.18 76 76 version: 4.1.18 77 + ts-pattern: 78 + specifier: ^5.9.0 79 + version: 5.9.0 77 80 typescript: 78 81 specifier: ^5.9.3 79 82 version: 5.9.3 ··· 1951 1954 peerDependencies: 1952 1955 typescript: '>=4.8.4' 1953 1956 1957 + ts-pattern@5.9.0: 1958 + resolution: {integrity: sha512-6s5V71mX8qBUmlgbrfL33xDUwO0fq48rxAu2LBE11WBeGdpCPOsXksQbZJHvHwhrd3QjUusd3mAOM5Gg0mFBLg==} 1959 + 1954 1960 tslib@2.8.1: 1955 1961 resolution: {integrity: sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==} 1956 1962 ··· 3729 3735 ts-api-utils@2.4.0(typescript@5.9.3): 3730 3736 dependencies: 3731 3737 typescript: 5.9.3 3738 + 3739 + ts-pattern@5.9.0: {} 3732 3740 3733 3741 tslib@2.8.1: 3734 3742 optional: true