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:
Bradley Shellnut 2023-12-26 17:26:39 -08:00
parent c91cb72b38
commit daa9a628d1
25 changed files with 523 additions and 280 deletions

View file

@ -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",

File diff suppressed because it is too large Load diff

View file

@ -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,

View file

@ -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>

View 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>

View 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"];

View file

@ -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);

View file

@ -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, '/');
};

View file

@ -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 {

View file

@ -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({

View file

@ -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;

View file

@ -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) {

View file

@ -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 };
};

View file

@ -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;

View file

@ -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) {

View file

@ -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}

View file

@ -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) {

View file

@ -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, '/');
}
};

View file

@ -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);
}

View file

@ -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' });
}
}

View file

@ -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];

View file

@ -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];

View file

@ -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);

View file

@ -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' });
}
}

View file

@ -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';