diff --git a/bun.lockb b/bun.lockb index 3cfcf9c..a59d4e6 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/migrations/0004_fix_tables.sql b/migrations/0004_fix_tables.sql new file mode 100644 index 0000000..d87a9e1 --- /dev/null +++ b/migrations/0004_fix_tables.sql @@ -0,0 +1,44 @@ +-- Migration number: 0004 2024-11-25T09:05:22.577Z +CREATE TABLE user_tmp ( + `id` TEXT NOT NULL PRIMARY KEY, + `name` TEXT NOT NULL, + `avatar_hash` TEXT NULL +); + +CREATE TABLE session_tmp ( + `id` TEXT NOT NULL PRIMARY KEY, + `user_id` TEXT NOT NULL REFERENCES user_tmp(id), + `expires_at` INTEGER NOT NULL +); + +CREATE TABLE game_tmp ( + `id` TEXT NOT NULL PRIMARY KEY, -- uuid + `user_id` TEXT NULL REFERENCES user_tmp(id), + `mode` TEXT NOT NULL, + `time` INTEGER NOT NULL, + `total_score` INTEGER NOT NULL, + `stops_type` TEXT NOT NULL +); + +CREATE TABLE round_tmp ( + `id` INTEGER PRIMARY KEY ASC, -- rowid alias, should be automatically assigned + `game_id` TEXT NOT NULL REFERENCES game_tmp(id), + `points` INTEGER NOT NULL, + `distance` INTEGER NOT NULL, + `stop_name` TEXT NOT NULL +); + +INSERT INTO user_tmp(id, name, avatar_hash) SELECT id, name, avatar_hash FROM user; +INSERT INTO session_tmp(id, user_id, expires_at) SELECT id, user_id, expires_at FROM session; +INSERT INTO game_tmp(id, user_id, mode, time, total_score, stops_type) SELECT id, user_id, mode, time, total_score, stops_type FROM game; +INSERT INTO round_tmp(id, game_id, points, distance, stop_name) SELECT id, game_id, points, distance, stop_name FROM round; + +DROP TABLE round; +DROP TABLE game; +DROP TABLE session; +DROP TABLE user; + +ALTER TABLE user_tmp RENAME TO user; +ALTER TABLE session_tmp RENAME TO session; +ALTER TABLE game_tmp RENAME TO game; +ALTER TABLE round_tmp RENAME TO round; diff --git a/package.json b/package.json index 49dd5db..feed405 100644 --- a/package.json +++ b/package.json @@ -15,6 +15,7 @@ "@oslojs/crypto": "^1.0.1", "@oslojs/encoding": "^1.1.0", "arctic": "^2.2.2", + "destr": "^2.0.3", "leaflet": "^1.9.4", "leaflet-defaulticon-compatibility": "^0.1.2" }, diff --git a/src/lib/auth/index.ts b/src/lib/auth/index.ts index 2f5b7a6..654eb66 100644 --- a/src/lib/auth/index.ts +++ b/src/lib/auth/index.ts @@ -13,3 +13,8 @@ export interface User { name: string; avatarHash: string; } + +export interface CookieData { + state: string; + next: string; +} diff --git a/src/lib/index.ts b/src/lib/index.ts index a18e6dc..6b8582d 100644 --- a/src/lib/index.ts +++ b/src/lib/index.ts @@ -13,7 +13,7 @@ let lazyTram: [GeoJSON.Feature, string][] | null = null; let lazyStops: GeoJSON.Feature[] | null = null; export async function createGame( - userId: string, + userId: string | null, mode: string, stopsType: string, stops: GeoJSON.Feature[], diff --git a/src/routes/+page.server.ts b/src/routes/+page.server.ts index d82fc0f..353be24 100644 --- a/src/routes/+page.server.ts +++ b/src/routes/+page.server.ts @@ -1,10 +1,5 @@ -import { redirect } from "@sveltejs/kit"; import type { PageServerLoad } from "./$types"; export const load: PageServerLoad = async ({ locals }) => { - if (locals.user === null) { - redirect(302, "/login"); - } else { - return { user: locals.user }; - } + return { user: locals.user }; }; diff --git a/src/routes/api/game/+server.ts b/src/routes/api/game/+server.ts index 0ee5b31..30e6730 100644 --- a/src/routes/api/game/+server.ts +++ b/src/routes/api/game/+server.ts @@ -6,9 +6,7 @@ export const GET: RequestHandler = async ({ url, platform, locals }) => { const db = platform?.env?.TCL_GUESSR_D1 ?? null; if (db === null) error(500, "could not connect to d1"); - const user = locals.user; - if (user === null) error(401, "not logged in"); - + const userId = locals.user?.id ?? null; const options = parseOptions(url); const crossingStops = await getMergedStops(options.stopsType); @@ -18,7 +16,7 @@ export const GET: RequestHandler = async ({ url, platform, locals }) => { error(400, "could not select random stop"); } - const gameData = await createGame(user.id, options.mode, options.stopsType, randomStops, db); + const gameData = await createGame(userId, options.mode, options.stopsType, randomStops, db); return Response.json(gameData); }; diff --git a/src/routes/api/save/+server.ts b/src/routes/api/save/+server.ts new file mode 100644 index 0000000..d57992b --- /dev/null +++ b/src/routes/api/save/+server.ts @@ -0,0 +1,16 @@ +import { error, redirect } from "@sveltejs/kit"; +import type { RequestHandler } from "./$types"; + +export const GET: RequestHandler = async ({ locals, platform, url }) => { + const db = platform?.env?.TCL_GUESSR_D1 ?? null; + if (db === null) error(500, "could not connect to d1"); + + const gameId = url.searchParams.get("id") ?? null; + if (gameId === null) error(400, "no game id specified"); + + if (locals.user === null) error(401); + + await db.prepare("UPDATE game SET user_id = ? WHERE id = ?").bind(locals.user.id, gameId).run(); + + redirect(302, `/results?gameId=${gameId}`); +}; diff --git a/src/routes/login/+server.ts b/src/routes/login/+server.ts index b32ac72..12361bf 100644 --- a/src/routes/login/+server.ts +++ b/src/routes/login/+server.ts @@ -2,18 +2,20 @@ import { redirect } from "@sveltejs/kit"; import { generateState } from "arctic"; import { discord } from "$lib/auth/discord"; import type { RequestHandler } from "./$types"; +import type { CookieData } from "$lib/auth"; + +export const GET: RequestHandler = async ({ cookies, url }) => { + const cookie: CookieData = { state: generateState(), next: url.searchParams.get("next") ?? "/" }; -export const GET: RequestHandler = async ({ cookies }) => { - const state = generateState(); const scopes = ["identify"]; - const url = discord.createAuthorizationURL(state, scopes); + const authUrl = discord.createAuthorizationURL(cookie.state, scopes); - cookies.set("discord_oauth_state", state, { + cookies.set("discord_oauth_state", JSON.stringify(cookie), { path: "/", httpOnly: true, maxAge: 60 * 10, sameSite: "lax", }); - redirect(302, url); + redirect(302, authUrl); }; diff --git a/src/routes/login/callback/+server.ts b/src/routes/login/callback/+server.ts index 46ada4a..2d82b19 100644 --- a/src/routes/login/callback/+server.ts +++ b/src/routes/login/callback/+server.ts @@ -1,9 +1,11 @@ import { error, redirect } from "@sveltejs/kit"; -import type { RequestHandler } from "./$types"; import { discord, type DiscordUser } from "$lib/auth/discord"; import { OAuth2RequestError, type OAuth2Tokens } from "arctic"; import { createSession, generateSessionToken } from "$lib/auth/session"; import { setSessionTokenCookie } from "$lib/auth/cookie"; +import { destr } from "destr"; +import type { RequestHandler } from "./$types"; +import type { CookieData } from "$lib/auth"; export const GET: RequestHandler = async ({ platform, url, cookies, fetch }) => { const db = platform?.env?.TCL_GUESSR_D1 ?? null; @@ -11,13 +13,13 @@ export const GET: RequestHandler = async ({ platform, url, cookies, fetch }) => const code = url.searchParams.get("code"); const state = url.searchParams.get("state"); - const storedState = cookies.get("discord_oauth_state") ?? null; + const cookie = destr(cookies.get("discord_oauth_state")) ?? null; - if (code === null || state === null || storedState === null) { + if (code === null || state === null || cookie === null) { error(400, "missing things from oauth2 response"); } - if (state !== storedState) { + if (state !== cookie.state) { error(400, "state does not match"); } @@ -49,5 +51,5 @@ export const GET: RequestHandler = async ({ platform, url, cookies, fetch }) => const session = await createSession(sessionToken, discordUser.id, db); setSessionTokenCookie(cookies, sessionToken, session.expiresAt); - redirect(302, "/"); + redirect(302, cookie.next); }; diff --git a/src/routes/results/+page.server.ts b/src/routes/results/+page.server.ts index 61c10eb..d37b0ea 100644 --- a/src/routes/results/+page.server.ts +++ b/src/routes/results/+page.server.ts @@ -4,12 +4,13 @@ import { error } from "@sveltejs/kit"; interface JoinedRoundScore { mode: string; total_score: number; + name: string; points: number; distance: number; stop_name: string; } -export const load: PageServerLoad = async ({ url, platform }) => { +export const load: PageServerLoad = async ({ locals, url, platform }) => { const db = platform?.env?.TCL_GUESSR_D1; if (!db) error(500, "could not connect to d1"); @@ -18,10 +19,10 @@ export const load: PageServerLoad = async ({ url, platform }) => { const { results } = await db .prepare( - "SELECT game.mode, game.total_score, round.points, round.distance, round.stop_name FROM game INNER JOIN round ON round.game_id = game.id WHERE game.id = ?;", + "SELECT game.mode, game.total_score, user.name, round.points, round.distance, round.stop_name FROM game INNER JOIN round ON round.game_id = game.id LEFT JOIN user ON user.id = game.user_id WHERE game.id = ?;", ) .bind(gameId) .all(); - return { rounds: results, gameId }; + return { rounds: results, gameId, loggedIn: locals.user !== null }; }; diff --git a/src/routes/results/+page.svelte b/src/routes/results/+page.svelte index fd84395..12f416f 100644 --- a/src/routes/results/+page.svelte +++ b/src/routes/results/+page.svelte @@ -10,6 +10,9 @@ const totalScore = props.data.rounds[0].total_score; const mode = props.data.rounds[0].mode; + const name = props.data.rounds[0].name; + + const saveParams = new URLSearchParams({ next: `/api/save?id=${props.data.gameId}` });
@@ -19,6 +22,14 @@ mode: {mode} + {#if name !== null} + joué par {name} + {:else if props.data.loggedIn} + joué par un inconnu... + {:else} + connectez vous pour sauvegarder votre score! + {/if} + {#each props.data.rounds as round}