Interceptors, redirectTo, refactor #17

Merged
glazk0 merged 4 commits from dev into main 2024-04-01 00:33:52 +02:00
26 changed files with 164 additions and 150 deletions

8
src/app.d.ts vendored
View file

@ -5,7 +5,10 @@ import type { User } from '$lib/types';
// for information about these interfaces
declare global {
namespace App {
// interface Error {}
interface Error {
message: string;
errorId: string;
}
interface Locals {
user: User | null;
}
@ -16,4 +19,5 @@ declare global {
}
}
export {};
export { };

View file

@ -1,4 +1,4 @@
import type { Handle } from '@sveltejs/kit';
import type { Handle, HandleFetch, HandleServerError } from '@sveltejs/kit';
import { API_URL } from '$env/static/private';
@ -30,3 +30,32 @@ export const handle: Handle = async ({ event, resolve }) => {
return resolve(event);
};
export const handleFetch: HandleFetch = async ({ request, fetch, event: { cookies } }) => {
const session = cookies.get('session');
if (!session) {
return fetch(request);
}
request = new Request(request, {
headers: {
...request.headers,
Authorization: `Bearer ${session}`
},
});
return fetch(request);
}
export const handleError: HandleServerError = async ({ error, status }) => {
const errorId = crypto.randomUUID();
console.error(`Error ID: ${errorId} - Status: ${status} - ${error}`);
return {
message: 'Whoops!',
errorId,
};
};

View file

@ -29,6 +29,7 @@ export interface Completion {
export interface Group {
id: number;
name: string;
playerCount: number;
chapter?: number;
puzzle?: number;
}

View file

@ -1,12 +1,17 @@
import { type ClassValue, clsx } from "clsx";
import { twMerge } from "tailwind-merge";
import { clsx, type ClassValue } from "clsx";
import { cubicOut } from "svelte/easing";
import type { TransitionConfig } from "svelte/transition";
import { twMerge } from "tailwind-merge";
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
export const handleRedirect = (path: string, url: URL) => {
const redirectTo = url.pathname + url.search;
return `/${path}?redirectTo=${encodeURIComponent(redirectTo)}`;
}
type FlyAndScaleParams = {
y?: number;
x?: number;

View file

@ -1,6 +0,0 @@
import { redirect, type ServerLoad } from '@sveltejs/kit';
export const load: ServerLoad = async ({ parent }) => {
const { user } = await parent();
if (!user) redirect(302, '/login');
};

View file

@ -2,7 +2,6 @@
import { navigating } from '$app/stores';
import { Loader, Navbar, Sidenav } from '$lib/components/layout';
import { Toaster } from '$lib/components/ui/sonner';
</script>
{#if $navigating}
@ -18,7 +17,6 @@
class="flex w-full flex-1 transform flex-col overflow-y-auto p-4 duration-300 ease-in-out"
>
<slot />
<Toaster position="top-right" />
</div>
</div>
</div>

View file

@ -4,18 +4,13 @@ import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { Chapter } from '$lib/types';
import { handleRedirect } from '$lib/utils';
export const load = (async ({ fetch, cookies, locals: { user } }) => {
export const load = (async ({ url, fetch, locals: { user } }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
const session = cookies.get('session');
const res = await fetch(`${API_URL}/chapters`, {
headers: {
Authorization: `Bearer ${session}`
}
});
const res = await fetch(`${API_URL}/chapters`);
if (!res.ok) {
return {

View file

@ -1,6 +1,8 @@
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user } }) => {
if (!user) redirect(302, '/login');
}) satisfies PageServerLoad;
import { handleRedirect } from '$lib/utils';
export const load: PageServerLoad = async ({ url, locals: { user } }) => {
if (!user) redirect(302, handleRedirect('login', url));
}

View file

@ -1,21 +1,16 @@
import { API_URL } from '$env/static/private';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { Chapter } from '$lib/types';
import { redirect } from '@sveltejs/kit';
import { handleRedirect } from '$lib/utils';
export const load = (async ({ locals: { user }, fetch, cookies }) => {
export const load = (async ({ url, locals: { user }, fetch }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
const session = cookies.get('session');
const res = await fetch(`${API_URL}/chapters`, {
headers: {
Authorization: `Bearer ${session}`
}
});
const res = await fetch(`${API_URL}/chapters`);
if (!res.ok) {
return {

View file

@ -3,19 +3,14 @@ import { API_URL } from '$env/static/private';
import type { PageServerLoad } from './$types';
import type { Chapter } from '$lib/types';
import { handleRedirect } from '$lib/utils';
import { redirect } from '@sveltejs/kit';
export const load = (async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
export const load = (async ({ url, locals: { user }, fetch, params: { chapterId } }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
const session = cookies.get('session');
const res = await fetch(`${API_URL}/chapter/${chapterId}`, {
headers: {
Authorization: `Bearer ${session}`
}
});
const res = await fetch(`${API_URL}/chapter/${chapterId}`);
if (!res.ok) {
redirect(302, '/chapters');

View file

@ -3,19 +3,14 @@ import { API_URL } from '$env/static/private';
import type { Actions, PageServerLoad } from './$types';
import type { Chapter, Group } from '$lib/types';
import { handleRedirect } from '$lib/utils';
import { redirect } from '@sveltejs/kit';
export const load = (async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
export const load: PageServerLoad = async ({ url, locals: { user }, fetch, params: { chapterId } }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
const session = cookies.get('session');
let res = await fetch(`${API_URL}/chapter/${chapterId}`, {
headers: {
Authorization: `Bearer ${session}`
}
});
let res = await fetch(`${API_URL}/chapter/${chapterId}`);
if (!res.ok) {
redirect(302, '/chapters');
@ -27,12 +22,7 @@ export const load = (async ({ locals: { user }, fetch, cookies, params: { chapte
redirect(302, '/chapters');
}
res = await fetch(`${API_URL}/groups/${chapter.id}`, {
headers:
{
Authorization: `Bearer ${session}`
}
});
res = await fetch(`${API_URL}/groups/${chapter.id}`);
if (!res.ok) {
redirect(302, `/chapters/${chapterId}`);
@ -45,23 +35,18 @@ export const load = (async ({ locals: { user }, fetch, cookies, params: { chapte
chapter,
groups
};
}) satisfies PageServerLoad;
};
export const actions: Actions = {
join: async ({ fetch, params: { chapterId }, cookies, request }) => {
join: async ({ fetch, params: { chapterId }, request }) => {
const data = await request.formData();
const name = data.get('name') as string;
const session = cookies.get('session');
const res = await fetch(`${API_URL}/groupJoin`, {
method: 'POST',
headers: {
Authorization: `Bearer ${session}`
},
body: JSON.stringify({
name,
chapter: parseInt(chapterId),
@ -78,7 +63,7 @@ export const actions: Actions = {
if (res.status === 403) {
return {
success: false,
message: 'Vous êtes déjà dans un groupe'
message: 'Vous êtes déjà dans un groupe ou le groupe est complet'
};
}
@ -94,19 +79,14 @@ export const actions: Actions = {
message: "Une erreur s'est produite"
};
},
leave: async ({ fetch, params: { chapterId }, cookies, request }) => {
leave: async ({ fetch, params: { chapterId }, request }) => {
const data = await request.formData();
const name = data.get('name') as string;
const session = cookies.get('session');
const res = await fetch(`${API_URL}/groupQuit`, {
method: 'POST',
headers: {
Authorization: `Bearer ${session}`
},
body: JSON.stringify({
name,
chapter: parseInt(chapterId),

View file

@ -17,6 +17,8 @@
export let data: PageData;
export let form: ActionData;
let limit = 4;
let name = '';
let submitting = false;
@ -58,19 +60,26 @@
</div>
<ul class="flex flex-col gap-2">
{#if filteredGroups.length === 0}
<li class="flex h-16 w-full items-center justify-center rounded border border-border bg-card">
<li
class="flex min-h-16 w-full items-center justify-center rounded border border-border bg-card"
>
<p class="text-muted-foreground">Aucun groupe trouvé</p>
</li>
{:else}
{#each filteredGroups as group (group.name)}
<li class="group relative flex h-full w-full flex-col rounded border border-border bg-card">
<div class="flex h-full w-full items-center justify-between gap-4 p-4">
<li
class="group relative flex min-h-16 w-full flex-col rounded border border-border bg-card"
>
<div class="flex items-center justify-between gap-4 p-4">
<div class="flex items-center gap-2">
<span class="font-semibold">
{group.name}
</span>
<span class="text-muted-foreground">
{group.playerCount} membres
</span>
</div>
<div>
{#if group.playerCount < limit}
{#if $page.data.user?.groups.some((g) => g.name === group.name)}
<form
method="post"
@ -127,7 +136,7 @@
</Button>
</form>
{/if}
</div>
{/if}
</div>
</li>
{/each}

View file

@ -1,15 +1,16 @@
import { API_URL } from "$env/static/private";
import { fail, redirect, type Actions } from "@sveltejs/kit";
import type { PageServerLoad } from "./$types";
import { superValidate } from "sveltekit-superforms";
import { zod } from "sveltekit-superforms/adapters";
import { API_URL } from "$env/static/private";
import { handleRedirect } from "$lib/utils";
import { groupSchema } from "$lib/validations/group";
export const load: PageServerLoad = async ({ params: { chapterId }, locals: { user } }) => {
export const load: PageServerLoad = async ({ url, params: { chapterId }, locals: { user } }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
if (user.groups.find(g => g.chapter === parseInt(chapterId))) {
redirect(302, `/chapters/${chapterId}/groups`);
@ -24,14 +25,12 @@ export const load: PageServerLoad = async ({ params: { chapterId }, locals: { us
};
export const actions: Actions = {
default: async ({ locals: { user }, fetch, request, cookies, params: { chapterId } }) => {
default: async ({ url, locals: { user }, fetch, request, params: { chapterId } }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
if (!chapterId) redirect(302, '/chapters');
const session = cookies.get('session');
const form = await superValidate(request, zod(groupSchema));
if (!form.valid) {
@ -40,9 +39,6 @@ export const actions: Actions = {
const res = await fetch(`${API_URL}/groupCreate`, {
method: 'POST',
headers: {
Authorization: `Bearer ${session}`
},
body: JSON.stringify({
...form.data,
chapter: parseInt(chapterId)

View file

@ -1,22 +1,17 @@
import { API_URL } from '$env/static/private';
import type { PageServerLoad } from './$types';
import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { LeaderboardEvent } from '$lib/types';
import { handleRedirect } from '$lib/utils';
export const load: PageServerLoad = async ({ locals: { user }, fetch, cookies, params: { chapterId } }) => {
export const load: PageServerLoad = async ({ url, locals: { user }, fetch, params: { chapterId } }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
if (!chapterId) redirect(302, '/');
const session = cookies.get('session');
const res = await fetch(`${API_URL}/leaderboard/${chapterId}`, {
headers: {
Authorization: `Bearer ${session}`
}
});
const res = await fetch(`${API_URL}/leaderboard/${chapterId}`);
if (!res.ok) return {
leaderboard: []

View file

@ -2,7 +2,9 @@ import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
export const load = (async ({ locals: { user }, params: { chapterId } }) => {
if (!user) redirect(302, '/login');
import { handleRedirect } from '$lib/utils';
export const load = (async ({ url, locals: { user }, params: { chapterId } }) => {
if (!user) redirect(302, handleRedirect('login', url));
redirect(302, chapterId ? `/chapters/${chapterId}` : `/chapters`);
}) satisfies PageServerLoad;

View file

@ -1,13 +1,14 @@
import { API_URL } from '$env/static/private';
import { error, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { compile } from 'mdsvex';
import type { PageServerLoad } from './$types';
import type Puzzle from '$lib/components/puzzle.svelte';
import type { Chapter } from '$lib/types';
import { handleRedirect } from '$lib/utils';
export const load = (async ({ locals: { user }, fetch, cookies, params: { chapterId, puzzleId } }) => {
if (!user) redirect(302, '/login');
export const load = (async ({ url, locals: { user }, fetch, cookies, params: { chapterId, puzzleId } }) => {
if (!user) redirect(302, handleRedirect('login', url));
const session = cookies.get('session');
@ -15,11 +16,7 @@ export const load = (async ({ locals: { user }, fetch, cookies, params: { chapte
redirect(302, `/chapters/${chapterId}`);
}
let res = await fetch(`${API_URL}/chapter/${chapterId}`, {
headers: {
Authorization: `Bearer ${session}`
}
});
let res = await fetch(`${API_URL}/chapter/${chapterId}`);
if (!res.ok) {
redirect(302, `/chapters`);
@ -38,20 +35,22 @@ export const load = (async ({ locals: { user }, fetch, cookies, params: { chapte
redirect(302, `/chapters/${chapterId}`);
}
res = await fetch(`${API_URL}/puzzle/${puzzleId}`, {
headers: {
Authorization: `Bearer ${session}`
}
});
res = await fetch(`${API_URL}/puzzle/${puzzleId}`);
if (!res.ok) {
error(404, 'Puzzle not found');
error(404, {
errorId: 'puzzle_not_found',
message: 'Puzzle not found'
});
}
const puzzle: Puzzle = await res.json();
if (!puzzle) {
error(404, 'Puzzle not found');
error(404, {
errorId: 'puzzle_not_found',
message: 'Puzzle not found'
});
}
const content = await compile(puzzle.content);

View file

@ -1,4 +0,0 @@
<script lang="ts">
</script>
TODO

View file

@ -4,18 +4,13 @@ import { redirect } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import type { Leaderboard } from '$lib/types';
import { handleRedirect } from '$lib/utils';
export const load = (async ({ locals: { user }, fetch, cookies }) => {
export const load = (async ({ url, locals: { user }, fetch }) => {
if (!user) redirect(302, '/login');
if (!user) redirect(302, handleRedirect('login', url));
const session = cookies.get('session');
const res = await fetch(`${API_URL}/leaderboard`, {
headers: {
Authorization: `Bearer ${session}`
}
});
const res = await fetch(`${API_URL}/leaderboard`);
if (!res.ok) {
return {

View file

@ -1,5 +1,7 @@
<script lang="ts">
import { page } from '$app/stores';
import { Button } from '$lib/components/ui/button';
import { cn } from '$lib/utils';
let routes = [
{
@ -14,7 +16,13 @@
<ul class="flex gap-2 lg:flex-col">
{#each routes as { name, href } (name)}
<li>
<Button class="w-full" variant="outline" {href}>{name}</Button>
<Button
class={cn('w-full', {
'bg-secondary': $page.url.pathname === href
})}
variant="outline"
{href}>{name}</Button
>
</li>
{/each}
</ul>

View file

@ -2,12 +2,14 @@ import { API_URL } from '$env/static/private';
import { fail, redirect, type Actions } from '@sveltejs/kit';
import type { PageServerLoad } from './$types';
import { settingSchema } from '$lib/validations/auth';
import { zod } from 'sveltekit-superforms/adapters';
import { setError, superValidate } from 'sveltekit-superforms/server';
export const load: PageServerLoad = async ({ locals: { user } }) => {
if (!user) redirect(302, '/login');
import { handleRedirect } from '$lib/utils';
import { settingSchema } from '$lib/validations/auth';
export const load: PageServerLoad = async ({ url, locals: { user } }) => {
if (!user) redirect(302, handleRedirect('login', url));
const form = await superValidate(user, zod(settingSchema));
@ -18,12 +20,10 @@ export const load: PageServerLoad = async ({ locals: { user } }) => {
};
export const actions: Actions = {
default: async ({ request, cookies, locals: { user } }) => {
default: async ({ request, fetch, locals: { user } }) => {
if (!user) return fail(401);
const session = cookies.get('session');
const form = await superValidate(request, zod(settingSchema));
if (!form.valid) {
@ -32,9 +32,6 @@ export const actions: Actions = {
const res = await fetch(`${API_URL}/user/settings`, {
method: 'POST',
headers: {
Authorization: `Bearer ${session}`
},
body: JSON.stringify({
...form.data
})

View file

@ -1,7 +0,0 @@
<script lang="ts">
import { Toaster } from '$lib/components/ui/sonner';
</script>
<slot />
<Toaster />

View file

@ -21,7 +21,7 @@ export const load: PageServerLoad = async ({ locals: { user } }) => {
};
export const actions: Actions = {
default: async ({ request, cookies, fetch }) => {
default: async ({ request, cookies, fetch, url: { searchParams } }) => {
const form = await superValidate(request, zod(loginSchema));
@ -52,6 +52,11 @@ export const actions: Actions = {
sameSite: 'strict',
});
const redirectTo = searchParams.get('redirectTo');
if (redirectTo)
redirect(302, `/${redirectTo.slice(1)}`);
redirect(302, '/');
}
};

View file

@ -53,7 +53,7 @@ export const actions: Actions = {
form
};
},
confirmation: async ({ request, cookies }) => {
confirmation: async ({ request, cookies, url: { searchParams } }) => {
const form = await superValidate(request, zod(registerConfirmationSchema));
if (!form.valid) {
@ -93,6 +93,11 @@ export const actions: Actions = {
sameSite: 'strict',
});
const redirectTo = searchParams.get('redirectTo');
if (redirectTo)
redirect(302, `/${redirectTo.slice(1)}`);
redirect(302, '/');
},
}
}

View file

@ -11,7 +11,6 @@
import Input from '$lib/components/ui/input/input.svelte';
import { registerConfirmationSchema, registerSchema } from '$lib/validations/auth';
import { enhance } from '$app/forms';
export let data: PageData;

14
src/routes/+error.svelte Normal file
View file

@ -0,0 +1,14 @@
<script lang="ts">
import { page } from '$app/stores';
import Button from '$lib/components/ui/button/button.svelte';
</script>
<div class="flex min-h-screen items-center justify-center">
<div class="text-center">
<h1 class="text-6xl font-bold text-red-500">Oops!</h1>
<p class="mt-4 text-xl">Apparement tu as navigué en eau trouble...</p>
<Button class="mt-4" href="/">Retour au port</Button>
<p class="mt-4 text-xs text-muted-foreground">{$page.error?.errorId}</p>
</div>
</div>

View file

@ -2,8 +2,11 @@
import '../app.css';
import { Metadata } from '$lib/components';
import { Toaster } from '$lib/components/ui/sonner';
</script>
<Metadata />
<slot />
<Toaster />