mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Upgrading to SvelteKit v2, fixing headers render, and adding random games search. Also add badge component and put in the Game details page.
This commit is contained in:
parent
c91cb72b38
commit
daa9a628d1
25 changed files with 523 additions and 280 deletions
13
package.json
13
package.json
|
|
@ -30,9 +30,10 @@
|
|||
"@melt-ui/svelte": "^0.66.3",
|
||||
"@playwright/test": "^1.40.1",
|
||||
"@resvg/resvg-js": "^2.4.1",
|
||||
"@sveltejs/adapter-auto": "^2.1.1",
|
||||
"@sveltejs/adapter-vercel": "^3.1.0",
|
||||
"@sveltejs/kit": "^1.30.3",
|
||||
"@sveltejs/adapter-auto": "^3.0.0",
|
||||
"@sveltejs/adapter-vercel": "^4.0.0",
|
||||
"@sveltejs/kit": "^2.0.0",
|
||||
"@sveltejs/vite-plugin-svelte": "^3.0.0",
|
||||
"@types/cookie": "^0.5.4",
|
||||
"@types/node": "^18.19.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.16.0",
|
||||
|
|
@ -64,8 +65,8 @@
|
|||
"ts-node": "^10.9.2",
|
||||
"tslib": "^2.6.1",
|
||||
"typescript": "^5.3.3",
|
||||
"vite": "^4.5.1",
|
||||
"vitest": "^0.34.6",
|
||||
"vite": "^5.0.0",
|
||||
"vitest": "^1.0.0",
|
||||
"zod": "^3.22.4"
|
||||
},
|
||||
"type": "module",
|
||||
|
|
@ -107,4 +108,4 @@
|
|||
"tailwindcss-animate": "^1.0.6",
|
||||
"zod-to-json-schema": "^3.22.3"
|
||||
}
|
||||
}
|
||||
}
|
||||
624
pnpm-lock.yaml
624
pnpm-lock.yaml
File diff suppressed because it is too large
Load diff
|
|
@ -1,7 +1,7 @@
|
|||
import * as Sentry from '@sentry/sveltekit';
|
||||
import { sequence } from '@sveltejs/kit/hooks';
|
||||
import type { Handle } from '@sveltejs/kit';
|
||||
import { dev } from '$app/environment';
|
||||
import { browser, dev } from '$app/environment';
|
||||
import { lucia } from '$lib/server/auth';
|
||||
|
||||
Sentry.init({
|
||||
|
|
@ -14,7 +14,10 @@ export const authentication: Handle = async function ({ event, resolve }) {
|
|||
const startTimer = Date.now();
|
||||
event.locals.startTimer = startTimer;
|
||||
|
||||
const ip = event.request.headers.get('x-forwarded-for') as string || event.getClientAddress();
|
||||
let ip = event.request.headers.get('x-forwarded-for') as string;
|
||||
if (!ip && browser) {
|
||||
ip = event.getClientAddress();
|
||||
}
|
||||
const country = event.request.headers.get('x-vercel-ip-country') as string || 'unknown';
|
||||
event.locals.session = {
|
||||
ip,
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@
|
|||
|
||||
<header>
|
||||
<div class="corner">
|
||||
<a href="/static" class="logo" title="Home">
|
||||
<a href="/" class="logo" title="Home">
|
||||
<div class="logo-image">
|
||||
<Logo />
|
||||
</div>
|
||||
|
|
|
|||
18
src/lib/components/ui/badge/badge.svelte
Normal file
18
src/lib/components/ui/badge/badge.svelte
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
<script lang="ts">
|
||||
import { cn } from "$lib/utils";
|
||||
import { badgeVariants, type Variant } from ".";
|
||||
|
||||
let className: string | undefined | null = undefined;
|
||||
export let href: string | undefined = undefined;
|
||||
export let variant: Variant = "default";
|
||||
export { className as class };
|
||||
</script>
|
||||
|
||||
<svelte:element
|
||||
this={href ? "a" : "span"}
|
||||
{href}
|
||||
class={cn(badgeVariants({ variant, className }))}
|
||||
{...$$restProps}
|
||||
>
|
||||
<slot />
|
||||
</svelte:element>
|
||||
22
src/lib/components/ui/badge/index.ts
Normal file
22
src/lib/components/ui/badge/index.ts
Normal file
|
|
@ -0,0 +1,22 @@
|
|||
import { tv, type VariantProps } from "tailwind-variants";
|
||||
export { default as Badge } from "./badge.svelte";
|
||||
|
||||
export const badgeVariants = tv({
|
||||
base: "inline-flex items-center border rounded-full px-2.5 py-0.5 text-xs font-semibold transition-colors focus:outline-none select-none focus:ring-2 focus:ring-ring focus:ring-offset-2",
|
||||
variants: {
|
||||
variant: {
|
||||
default:
|
||||
"bg-primary hover:bg-primary/80 border-transparent text-primary-foreground",
|
||||
secondary:
|
||||
"bg-secondary hover:bg-secondary/80 border-transparent text-secondary-foreground",
|
||||
destructive:
|
||||
"bg-destructive hover:bg-destructive/80 border-transparent text-destructive-foreground",
|
||||
outline: "text-foreground"
|
||||
}
|
||||
},
|
||||
defaultVariants: {
|
||||
variant: "default"
|
||||
}
|
||||
});
|
||||
|
||||
export type Variant = VariantProps<typeof badgeVariants>["variant"];
|
||||
|
|
@ -3,9 +3,6 @@ import { Lucia, TimeSpan } from 'lucia';
|
|||
import { PrismaAdapter } from '@lucia-auth/adapter-prisma';
|
||||
import { dev } from '$app/environment';
|
||||
import prisma_client from '$lib/prisma';
|
||||
import { webcrypto } from "node:crypto";
|
||||
|
||||
globalThis.crypto = webcrypto as Crypto;
|
||||
|
||||
const adapter = new PrismaAdapter(prisma_client.session, prisma_client.user);
|
||||
|
||||
|
|
|
|||
|
|
@ -2,5 +2,5 @@ import { redirect } from '@sveltejs/kit';
|
|||
import type { PageServerData } from './$types';
|
||||
|
||||
export const load: PageServerData = async function ({ locals }) {
|
||||
if (!locals?.user?.role?.includes('admin')) throw redirect(302, '/');
|
||||
if (!locals?.user?.role?.includes('admin')) redirect(302, '/');
|
||||
};
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import type { PageServerLoad } from './$types';
|
|||
export const load: PageServerLoad = async ({ fetch, url, locals }) => {
|
||||
const user = locals.user;
|
||||
if (!user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
// console.log('locals load', locals);
|
||||
|
|
@ -121,7 +121,7 @@ export const actions: Actions = {
|
|||
// }
|
||||
// });
|
||||
console.log('game not found');
|
||||
throw redirect(302, '/404');
|
||||
redirect(302, '/404');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -183,7 +183,7 @@ export const actions: Actions = {
|
|||
|
||||
if (!game) {
|
||||
console.log('game not found');
|
||||
throw redirect(302, '/404');
|
||||
redirect(302, '/404');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import prisma from '$lib/prisma';
|
|||
export async function load({ params, locals }) {
|
||||
const user = locals.user;
|
||||
if (!user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -81,7 +81,7 @@ export const actions: Actions = {
|
|||
}
|
||||
|
||||
if (!wishlist) {
|
||||
throw redirect(302, '/404');
|
||||
redirect(302, '/404');
|
||||
}
|
||||
|
||||
const wishlistItem = await prisma.wishlistItem.create({
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@ export const load: PageServerLoad = async (event) => {
|
|||
const user = event.locals.user;
|
||||
|
||||
if (!user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
form.data = {
|
||||
|
|
@ -36,7 +36,7 @@ export const actions: Actions = {
|
|||
|
||||
console.log('updating profile');
|
||||
if (!event.locals.user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
const user = event.locals.user;
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import { modifyListGameSchema } from '$lib/config/zod-schemas.js';
|
|||
|
||||
export async function load({ params, locals }) {
|
||||
if (!locals.user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
console.log('Wishlist load User id', locals.user.id);
|
||||
|
|
@ -31,7 +31,7 @@ export async function load({ params, locals }) {
|
|||
});
|
||||
|
||||
if (!wishlist) {
|
||||
throw redirect(302, '/404');
|
||||
redirect(302, '/404');
|
||||
}
|
||||
|
||||
console.log('wishlist', wishlist);
|
||||
|
|
@ -53,7 +53,7 @@ export const actions: Actions = {
|
|||
|
||||
try {
|
||||
if (!locals.user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
|
|
@ -69,7 +69,7 @@ export const actions: Actions = {
|
|||
// }
|
||||
// });
|
||||
console.log('game not found');
|
||||
throw redirect(302, '/404');
|
||||
redirect(302, '/404');
|
||||
}
|
||||
|
||||
if (game) {
|
||||
|
|
@ -103,14 +103,14 @@ export const actions: Actions = {
|
|||
// Create new wishlist
|
||||
create: async ({ locals }) => {
|
||||
if (!locals.user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
return error(405, 'Method not allowed');
|
||||
},
|
||||
// Delete a wishlist
|
||||
delete: async ({ locals }) => {
|
||||
if (!locals.user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
return error(405, 'Method not allowed');
|
||||
},
|
||||
|
|
@ -121,7 +121,7 @@ export const actions: Actions = {
|
|||
|
||||
try {
|
||||
if (!locals.user) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
|
||||
const game = await prisma.game.findUnique({
|
||||
|
|
@ -137,7 +137,7 @@ export const actions: Actions = {
|
|||
// }
|
||||
// });
|
||||
console.log('game not found');
|
||||
throw redirect(302, '/404');
|
||||
redirect(302, '/404');
|
||||
}
|
||||
|
||||
if (game) {
|
||||
|
|
|
|||
|
|
@ -2,6 +2,8 @@ import { superValidate } from 'sveltekit-superforms/server';
|
|||
import { search_schema } from '$lib/zodValidation';
|
||||
import type { MetaTagsProps } from 'svelte-meta-tags';
|
||||
import type { PageServerLoad } from './$types';
|
||||
import prisma from '$lib/prisma';
|
||||
import type { Game } from '@prisma/client';
|
||||
|
||||
export const load: PageServerLoad = async ({ fetch, url }) => {
|
||||
const image = {
|
||||
|
|
@ -41,5 +43,16 @@ export const load: PageServerLoad = async ({ fetch, url }) => {
|
|||
formData.name = formData?.q;
|
||||
const form = await superValidate(formData, search_schema);
|
||||
console.log('form', form);
|
||||
return { form, metaTagsChild: metaTags };
|
||||
|
||||
const count = 5;
|
||||
const ids: { id: string }[] = await prisma.$queryRaw`SELECT id FROM games ORDER BY RAND() LIMIT ${count}`;
|
||||
const randomGames: Game[] = await prisma.game.findMany({
|
||||
where: {
|
||||
id: {
|
||||
in: ids.map(id => id.id)
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
return { form, metaTagsChild: metaTags, randomGames };
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,11 +1,14 @@
|
|||
<script lang="ts">
|
||||
// import TextSearch from '$lib/components/search/textSearch/header.svelte';
|
||||
import RandomSearch from '$lib/components/search/random/index.svelte';
|
||||
import Game from '$lib/components/game/index.svelte';
|
||||
import logo from '$lib/assets/bored-game.png';
|
||||
|
||||
// import Random from '$lib/components/random/header.svelte';
|
||||
|
||||
export let data;
|
||||
|
||||
const { randomGames } = data;
|
||||
</script>
|
||||
|
||||
<h1>Search Boardgames!</h1>
|
||||
|
|
@ -23,6 +26,12 @@
|
|||
<!-- <TextSearch showButton advancedSearch data={data.form} /> -->
|
||||
</div>
|
||||
|
||||
<section>
|
||||
{#each randomGames as game (game.id)}
|
||||
<Game {game} />
|
||||
{/each}
|
||||
</section>
|
||||
|
||||
<style lang="scss">
|
||||
.game-search {
|
||||
display: grid;
|
||||
|
|
|
|||
|
|
@ -52,7 +52,7 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
|
|||
console.log('found game', game);
|
||||
|
||||
if (!game) {
|
||||
throw error(404, 'not found');
|
||||
error(404, 'not found');
|
||||
}
|
||||
|
||||
const currentDate = new Date();
|
||||
|
|
@ -60,6 +60,7 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
|
|||
game.last_sync_at === null ||
|
||||
currentDate.getDate() - game.last_sync_at.getDate() > 7 * 24 * 60 * 60 * 1000
|
||||
) {
|
||||
console.log('Syncing details because last sync is out of date');
|
||||
await syncGameAndConnectedData(locals, game, fetch);
|
||||
}
|
||||
|
||||
|
|
@ -107,7 +108,7 @@ export const load: PageServerLoad = async ({ params, locals, fetch }) => {
|
|||
console.log(error);
|
||||
}
|
||||
|
||||
throw error(404, 'not found');
|
||||
error(404, 'not found');
|
||||
};
|
||||
|
||||
async function syncGameAndConnectedData(locals: App.Locals, game: Game, eventFetch: Function) {
|
||||
|
|
|
|||
|
|
@ -8,6 +8,7 @@
|
|||
import type { PageData } from './$types';
|
||||
import { Button } from '$components/ui/button';
|
||||
import AddToList from '$components/AddToList.svelte';
|
||||
import Badge from '$components/ui/badge/badge.svelte';
|
||||
|
||||
$: existsInCollection = $collectionStore.find((item: SavedGameType) => item.id === game.id);
|
||||
$: existsInWishlist = $wishlistStore.find((item: SavedGameType) => item.id === game.id);
|
||||
|
|
@ -69,15 +70,19 @@
|
|||
</section>
|
||||
<section>
|
||||
<p>Categories</p>
|
||||
{#each game?.categories as category}
|
||||
<span>{category.name}</span>
|
||||
{/each}
|
||||
<div style='display: flex; gap: 0.25rem;'>
|
||||
{#each game?.categories as category}
|
||||
<Badge>{category.name}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
<section>
|
||||
<p>Mechanics</p>
|
||||
<div style='display: flex; gap: 0.25rem;'>
|
||||
{#each game?.mechanics as mechanic}
|
||||
<span>{mechanic.name}</span>
|
||||
<Badge>{mechanic.name}</Badge>
|
||||
{/each}
|
||||
</div>
|
||||
</section>
|
||||
<section class="description" class:show={seeMore} class:hide={!seeMore} style="margin-top: 2rem;">
|
||||
{@html game?.description}
|
||||
|
|
|
|||
|
|
@ -39,7 +39,7 @@ async function searchForGames(
|
|||
|
||||
if (response.status !== 404 && !response.ok) {
|
||||
console.log('Status from internal api not 200', response.status);
|
||||
throw error(response.status);
|
||||
error(response.status);
|
||||
}
|
||||
|
||||
const games = await response.json();
|
||||
|
|
@ -63,7 +63,7 @@ async function searchForGames(
|
|||
|
||||
if (!externalResponse.ok) {
|
||||
console.log('Status not 200', externalResponse.status);
|
||||
throw error(externalResponse.status);
|
||||
error(externalResponse.status);
|
||||
}
|
||||
|
||||
if (externalResponse.ok) {
|
||||
|
|
|
|||
|
|
@ -6,10 +6,10 @@ export const actions: Actions = {
|
|||
console.log('Signing out user');
|
||||
const sessionId = cookies.get(lucia.sessionCookieName);
|
||||
if (!locals.user || !sessionId) {
|
||||
throw redirect(302, '/login');
|
||||
redirect(302, '/login');
|
||||
}
|
||||
await lucia.invalidateSession(sessionId);
|
||||
// locals.auth.setSession(null); // remove cookie
|
||||
throw redirect(302, '/');
|
||||
redirect(302, '/');
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -102,11 +102,11 @@ export const actions: Actions = {
|
|||
};
|
||||
form.data.password = '';
|
||||
form.data.confirm_password = '';
|
||||
throw error(500, message);
|
||||
error(500, message);
|
||||
}
|
||||
|
||||
event.cookies.set(sessionCookie.name, sessionCookie.value, sessionCookie.attributes);
|
||||
throw redirect(302, '/');
|
||||
redirect(302, '/');
|
||||
// const message = { type: 'success', message: 'Signed Up!' } as const;
|
||||
// throw flashRedirect(message, event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export async function GET({ url, locals, params }) {
|
|||
console.log('username', locals?.user?.id);
|
||||
|
||||
if (!session) {
|
||||
throw error(401, { message: 'Unauthorized' });
|
||||
error(401, { message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
let collection = await prisma.collection.findUnique({
|
||||
|
|
@ -27,7 +27,7 @@ export async function GET({ url, locals, params }) {
|
|||
|
||||
if (!collection) {
|
||||
console.log('Collection was not found');
|
||||
throw error(404, { message: 'Collection was not found' });
|
||||
error(404, { message: 'Collection was not found' });
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -68,6 +68,6 @@ export async function GET({ url, locals, params }) {
|
|||
return json(collection_items);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw error(500, { message: 'Something went wrong' });
|
||||
error(500, { message: 'Something went wrong' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
4
src/routes/api/external/game/[id]/+server.ts
vendored
4
src/routes/api/external/game/[id]/+server.ts
vendored
|
|
@ -10,7 +10,7 @@ export async function GET({ params, url }) {
|
|||
|
||||
// TODO: Debounce excessive calls and possibly throttle
|
||||
if (isNaN(game_id) || !isFinite(game_id)) {
|
||||
throw error(400, { message: 'Invalid game id' });
|
||||
error(400, { message: 'Invalid game id' });
|
||||
}
|
||||
|
||||
const client = BggClient.Create();
|
||||
|
|
@ -19,7 +19,7 @@ export async function GET({ params, url }) {
|
|||
});
|
||||
|
||||
if (!response || response.length === 0) {
|
||||
throw error(404, { message: 'No results found in external search' });
|
||||
error(404, { message: 'No results found in external search' });
|
||||
}
|
||||
|
||||
const result = response[0];
|
||||
|
|
|
|||
6
src/routes/api/external/search/+server.ts
vendored
6
src/routes/api/external/search/+server.ts
vendored
|
|
@ -22,10 +22,10 @@ export async function GET({ url }) {
|
|||
} catch (e) {
|
||||
console.error(e);
|
||||
if (e instanceof ZodError) {
|
||||
throw error(400, { message: e.flatten().fieldErrors });
|
||||
error(400, { message: e.flatten().fieldErrors });
|
||||
}
|
||||
|
||||
throw error(500, { message: 'Something went wrong' });
|
||||
error(500, { message: 'Something went wrong' });
|
||||
}
|
||||
|
||||
const client = BggClient.Create();
|
||||
|
|
@ -38,7 +38,7 @@ export async function GET({ url }) {
|
|||
|
||||
if (!response || response.length === 0 || response[0]?.total === 0) {
|
||||
console.log('No results found in external search', response);
|
||||
throw error(404, { message: 'No results found in external search' });
|
||||
error(404, { message: 'No results found in external search' });
|
||||
}
|
||||
|
||||
const result = response[0];
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export const GET = async ({ url, locals }) => {
|
|||
});
|
||||
|
||||
if (!game) {
|
||||
throw error(404, { message: 'No games found' });
|
||||
error(404, { message: 'No games found' });
|
||||
}
|
||||
const games = [game];
|
||||
console.log('Games found in Exact Search API', JSON.stringify(games, null, 2));
|
||||
|
|
@ -52,7 +52,7 @@ export const GET = async ({ url, locals }) => {
|
|||
skip
|
||||
})) || [];
|
||||
if (games.length === 0) {
|
||||
throw error(404, { message: 'No games found' });
|
||||
error(404, { message: 'No games found' });
|
||||
}
|
||||
console.log('Games found in Search API', JSON.stringify(games, null, 2));
|
||||
return json(games);
|
||||
|
|
|
|||
|
|
@ -15,7 +15,7 @@ export async function GET({ url, locals, params }) {
|
|||
console.log('username', locals?.user?.id);
|
||||
|
||||
if (!session) {
|
||||
throw error(401, { message: 'Unauthorized' });
|
||||
error(401, { message: 'Unauthorized' });
|
||||
}
|
||||
|
||||
let collection = await prisma.collection.findUnique({
|
||||
|
|
@ -27,7 +27,7 @@ export async function GET({ url, locals, params }) {
|
|||
|
||||
if (!collection) {
|
||||
console.log('Collection was not found');
|
||||
throw error(404, { message: 'Collection was not found' });
|
||||
error(404, { message: 'Collection was not found' });
|
||||
}
|
||||
|
||||
try {
|
||||
|
|
@ -68,6 +68,6 @@ export async function GET({ url, locals, params }) {
|
|||
return json(collection_items);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
throw error(500, { message: 'Something went wrong' });
|
||||
error(500, { message: 'Something went wrong' });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
import adapter from '@sveltejs/adapter-vercel';
|
||||
import { vitePreprocess } from '@sveltejs/kit/vite';
|
||||
import { vitePreprocess } from '@sveltejs/vite-plugin-svelte';
|
||||
import { preprocessMeltUI } from '@melt-ui/pp';
|
||||
import sequence from 'svelte-sequential-preprocessor';
|
||||
|
||||
|
|
|
|||
Loading…
Reference in a new issue