mirror of
https://github.com/BradNut/boredgame
synced 2025-09-08 17:40:22 +00:00
Adding separate endpoint for fetching games.
This commit is contained in:
parent
ea533499f2
commit
2a5eafdc1a
10 changed files with 339 additions and 138 deletions
6
.vscode/settings.json
vendored
Normal file
6
.vscode/settings.json
vendored
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
{
|
||||
"cSpell.words": [
|
||||
"kickstarter",
|
||||
"msrp"
|
||||
]
|
||||
}
|
||||
|
|
@ -36,6 +36,7 @@
|
|||
"dependencies": {
|
||||
"@fontsource/fira-mono": "^4.5.7",
|
||||
"@lukeed/uuid": "^2.0.0",
|
||||
"cookie": "^0.5.0"
|
||||
"cookie": "^0.5.0",
|
||||
"zod": "^3.14.4"
|
||||
}
|
||||
}
|
||||
|
|
@ -23,11 +23,13 @@ specifiers:
|
|||
svelte-preprocess: ^4.10.6
|
||||
tslib: ^2.3.1
|
||||
typescript: ^4.6.3
|
||||
zod: ^3.14.4
|
||||
|
||||
dependencies:
|
||||
'@fontsource/fira-mono': 4.5.7
|
||||
'@lukeed/uuid': 2.0.0
|
||||
cookie: 0.5.0
|
||||
zod: 3.14.4
|
||||
|
||||
devDependencies:
|
||||
'@playwright/test': 1.21.1
|
||||
|
|
@ -2823,3 +2825,7 @@ packages:
|
|||
dependencies:
|
||||
buffer-crc32: 0.2.13
|
||||
dev: true
|
||||
|
||||
/zod/3.14.4:
|
||||
resolution: {integrity: sha512-U9BFLb2GO34Sfo9IUYp0w3wJLlmcyGoMd75qU9yf+DrdGA4kEx6e+l9KOkAlyUO0PSQzZCa3TR4qVlcmwqSDuw==}
|
||||
dev: false
|
||||
|
|
|
|||
0
src/components/game.svelte
Normal file
0
src/components/game.svelte
Normal file
|
|
@ -59,7 +59,7 @@ export function enhance(
|
|||
|
||||
const url = new URL(form.action);
|
||||
url.search = url.hash = '';
|
||||
// invalidate(url.href);
|
||||
invalidate(url.href);
|
||||
} else if (error) {
|
||||
error({ data, form, error: null, response });
|
||||
} else {
|
||||
|
|
|
|||
72
src/lib/types.ts
Normal file
72
src/lib/types.ts
Normal file
|
|
@ -0,0 +1,72 @@
|
|||
export type Game = {
|
||||
id: string;
|
||||
handle: string;
|
||||
name: string;
|
||||
url: string;
|
||||
edit_url: string;
|
||||
price: number;
|
||||
price_ca: number;
|
||||
price_uk: number;
|
||||
price_au: number;
|
||||
msrp: number;
|
||||
year_published: number;
|
||||
min_players: number;
|
||||
max_players: number;
|
||||
min_playtime: number;
|
||||
max_playtime: number;
|
||||
min_age: number;
|
||||
description: string;
|
||||
players: string;
|
||||
playtime: string;
|
||||
}
|
||||
|
||||
export type SearchQuery = {
|
||||
client_id: string;
|
||||
limit?: number;
|
||||
skip?: number;
|
||||
ids?: string[];
|
||||
list_id?: string;
|
||||
kickstarter?: boolean;
|
||||
random?: boolean;
|
||||
name?: string;
|
||||
exact?: boolean;
|
||||
fuzzy_match?: boolean;
|
||||
designer?: string;
|
||||
publisher?: string;
|
||||
artist?: string;
|
||||
mechanics?: string;
|
||||
categories?: string;
|
||||
order_by?: string;
|
||||
ascending?: boolean;
|
||||
min_players?: number;
|
||||
max_players?: number;
|
||||
min_playtime?: number;
|
||||
max_playtime?: number;
|
||||
min_age?: number;
|
||||
year_published?: number;
|
||||
gt_min_players?: number;
|
||||
gt_max_players?: number;
|
||||
gt_min_playtime?: number;
|
||||
gt_max_playtime?: number;
|
||||
gt_min_age?: number;
|
||||
gt_year_published?: number;
|
||||
gt_price?: bigint;
|
||||
gt_msrp?: bigint;
|
||||
gt_discount?: bigint;
|
||||
gt_reddit_count?: number;
|
||||
gt_reddit_week_count?: number;
|
||||
gt_reddit_day_count?: number;
|
||||
lt_min_players?: number;
|
||||
lt_max_players?: number;
|
||||
lt_min_playtime?: number;
|
||||
lt_max_playtime?: number;
|
||||
lt_min_age?: number;
|
||||
lt_year_published?: number;
|
||||
lt_price?: bigint;
|
||||
lt_msrp?: bigint;
|
||||
lt_discount?: bigint;
|
||||
lt_reddit_count?: number;
|
||||
lt_reddit_week_count?: number;
|
||||
lt_reddit_day_count?: number;
|
||||
fields?: string;
|
||||
}
|
||||
30
src/lib/zodValidation.ts
Normal file
30
src/lib/zodValidation.ts
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
import { z } from "zod";
|
||||
|
||||
export const BoardGameSearch = z.object({
|
||||
minAge: z.number(),
|
||||
maxAge: z.number(),
|
||||
minPlayers: z.number(),
|
||||
maxPlayers: z.number(),
|
||||
});
|
||||
|
||||
export const Game = z.object({
|
||||
id: z.string(),
|
||||
handle: z.string(),
|
||||
name: z.string(),
|
||||
url: z.string(),
|
||||
edit_url: z.string(),
|
||||
price: z.number(),
|
||||
price_ca: z.number(),
|
||||
price_uk: z.number(),
|
||||
price_au: z.number(),
|
||||
msrp: z.number(),
|
||||
year_published: z.number(),
|
||||
min_players: z.number(),
|
||||
max_players: z.number(),
|
||||
min_playtime: z.number(),
|
||||
max_playtime: z.number(),
|
||||
min_age: z.number(),
|
||||
description: z.string(),
|
||||
players: z.string(),
|
||||
playtime: z.string(),
|
||||
});
|
||||
62
src/routes/api/games/index.ts
Normal file
62
src/routes/api/games/index.ts
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
import type { SearchQuery } from "$lib/types";
|
||||
import type { RequestHandler } from "@sveltejs/kit";
|
||||
|
||||
export const post: RequestHandler = async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const queryParams : SearchQuery = {
|
||||
order_by: 'rank',
|
||||
ascending: false,
|
||||
limit: 20,
|
||||
client_id: import.meta.env.VITE_PUBLIC_CLIENT_ID,
|
||||
}
|
||||
|
||||
const minAge = form.get('minAge');
|
||||
const minPlayers = form.get('minPlayers');
|
||||
const maxPlayers = form.get('maxPlayers');
|
||||
const random = form.get('random') === 'on' || false;
|
||||
|
||||
if (minPlayers) queryParams.gt_min_players = (+minPlayers === 1 ? 0 : (+minPlayers - 1));
|
||||
if (maxPlayers) queryParams.lt_max_players = +maxPlayers + 1;
|
||||
if (minAge) queryParams.gt_min_age = +minAge === 1 ? 0 : +minAge - 1;
|
||||
queryParams.random = random;
|
||||
|
||||
const newQueryParams = {};
|
||||
for (const key in queryParams) {
|
||||
newQueryParams[key] = new String(queryParams[key]);
|
||||
}
|
||||
const urlQueryParams = new URLSearchParams(newQueryParams);
|
||||
console.log('urlQueryParams', JSON.stringify(urlQueryParams, null, 2));
|
||||
|
||||
const url = `https://api.boardgameatlas.com/api/search${urlQueryParams ? `?${urlQueryParams}` : ''}`
|
||||
const response = await fetch(url, {
|
||||
method: 'get',
|
||||
headers: {
|
||||
'content-type': 'application/json'
|
||||
},
|
||||
});
|
||||
console.log('response', response);
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
body: {
|
||||
games: []
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (response.status === 200) {
|
||||
const gameResponse = await response.json();
|
||||
const games = gameResponse?.games;
|
||||
console.log('games', games);
|
||||
return {
|
||||
body: {
|
||||
games: gameResponse?.games,
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return {
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
|
|
@ -1,34 +1,27 @@
|
|||
<script lang="ts">
|
||||
import { enhance } from "$lib/form";
|
||||
import type { Game } from "$lib/types";
|
||||
import { Checkbox, NumberInput, Tooltip } from "carbon-components-svelte";
|
||||
// import { enhance } from "$lib/form";
|
||||
|
||||
let games: Game[] = [];
|
||||
let submitting = false;
|
||||
|
||||
async function handleSubmit(event: SubmitEvent) {
|
||||
submitting = true;
|
||||
const form = event.target as HTMLFormElement;
|
||||
const response = await fetch('/api/games', {
|
||||
method: 'post',
|
||||
headers: { accept: 'application/json' },
|
||||
body: new FormData(form)
|
||||
});
|
||||
const responseData = await response.json();
|
||||
submitting = false;
|
||||
games = responseData?.games;
|
||||
}
|
||||
|
||||
let minAge = 0;
|
||||
let maxAge = 0;
|
||||
let minPlayers = 0;
|
||||
let maxPlayers = 0;
|
||||
let loading = false;
|
||||
type Game = {
|
||||
id: string;
|
||||
handle: string;
|
||||
name: string;
|
||||
url: string;
|
||||
edit_url: string;
|
||||
price: number;
|
||||
price_ca: number;
|
||||
price_uk: number;
|
||||
price_au: number;
|
||||
msrp: number;
|
||||
year_published: number;
|
||||
min_players: number;
|
||||
max_players: number;
|
||||
min_playtime: number;
|
||||
max_playtime: number;
|
||||
min_age: number;
|
||||
description: string;
|
||||
players: string;
|
||||
playtime: string;
|
||||
}
|
||||
|
||||
export let games: Game[];
|
||||
let minPlayers = 1;
|
||||
let maxPlayers = 1;
|
||||
</script>
|
||||
|
||||
|
||||
|
|
@ -37,44 +30,62 @@
|
|||
</svelte:head>
|
||||
|
||||
<div class="game-form">
|
||||
<form
|
||||
action="/games"
|
||||
method="post"
|
||||
use:enhance={{
|
||||
pending: () => {
|
||||
loading = true;
|
||||
},
|
||||
result: async ({ data, form, response }) => {
|
||||
loading = false;
|
||||
console.log(JSON.stringify(data))
|
||||
}
|
||||
}}
|
||||
>
|
||||
<fieldset aria-busy={loading} disabled={loading}>
|
||||
<label for="minAge">Min Age:
|
||||
<input id="minAge" name="minAge" type="range" min="0" max="120" step="1" bind:value={minAge} />
|
||||
{minAge}
|
||||
</label>
|
||||
<label for="maxAge">Max Age:
|
||||
<input id="maxAge" name="maxAge" type="range" min="0" max="120" step="1" bind:value={maxAge} />
|
||||
{maxAge}
|
||||
</label>
|
||||
<label for="minPlayers">Min Players:
|
||||
<input id="minPlayers" name="minPlayers" type="range" min="1" max="50" step="1" bind:value={minPlayers} />
|
||||
{minPlayers}
|
||||
</label>
|
||||
<label for="maxPlayers">Max Players:
|
||||
<input id="maxPlayers" name="maxPlayers" type="range" min="1" max="50" step="1" bind:value={maxPlayers} />
|
||||
{maxPlayers}
|
||||
</label>
|
||||
<form on:submit|preventDefault={handleSubmit} method="post">
|
||||
<fieldset aria-busy={submitting} disabled={submitting}>
|
||||
<div>
|
||||
<NumberInput
|
||||
name="minAge"
|
||||
min={0}
|
||||
max={120}
|
||||
bind:value={minAge}
|
||||
invalidText="Number must be between 0 and 120"
|
||||
label="Min Age"
|
||||
/>
|
||||
<Checkbox name="exactMinAge" labelText="Exact?" />
|
||||
</div>
|
||||
<div>
|
||||
<NumberInput
|
||||
name="minPlayers"
|
||||
min={1}
|
||||
max={50}
|
||||
bind:value={minPlayers}
|
||||
invalidText="Number must be between 1 and 50"
|
||||
label="Min Players"
|
||||
/>
|
||||
<div>
|
||||
<Checkbox name="exactMinPlayers" labelText="Exact?" />
|
||||
<Tooltip>
|
||||
<p id="tooltip-body">
|
||||
Resources are provisioned based on your account's organization.
|
||||
</p>
|
||||
</Tooltip>
|
||||
</div>
|
||||
</div>
|
||||
<div>
|
||||
<NumberInput
|
||||
name="maxPlayers"
|
||||
min={1}
|
||||
max={50}
|
||||
bind:value={maxPlayers}
|
||||
invalidText="Number must be between 1 and 50"
|
||||
label="Max Players"
|
||||
/>
|
||||
<Checkbox name="exactMaxPlayers" labelText="Exact?" />
|
||||
</div>
|
||||
</fieldset>
|
||||
<button type="submit" disabled={submitting}>Submit</button>
|
||||
</form>
|
||||
<form on:submit|preventDefault={handleSubmit} method="post">
|
||||
<fieldset aria-busy={submitting} disabled={submitting}>
|
||||
<input type="checkbox" id="random" name="random" hidden checked />
|
||||
<button type="submit" disabled={submitting}>🎲</button>
|
||||
</fieldset>
|
||||
<button type="submit" disabled={loading}>Submit</button>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
<div class="games">
|
||||
<h1>Games</h1>
|
||||
{#each games as game (game.id)}
|
||||
{#each games as game}
|
||||
<section>
|
||||
<div>
|
||||
<h2>{game.name}</h2>
|
||||
|
|
@ -100,6 +111,13 @@
|
|||
font-weight: 600;
|
||||
}
|
||||
|
||||
button {
|
||||
border-radius: 4px;
|
||||
margin: 0;
|
||||
padding: 0.2rem;
|
||||
background-color: palegreen;
|
||||
}
|
||||
|
||||
.games {
|
||||
display: grid;
|
||||
gap: 2rem;
|
||||
|
|
@ -110,7 +128,13 @@
|
|||
}
|
||||
|
||||
.game-form {
|
||||
display: flex;
|
||||
place-items: center;
|
||||
}
|
||||
|
||||
.game-form fieldset {
|
||||
display: grid;
|
||||
grid-template-columns: 1fr;
|
||||
gap: 1rem;
|
||||
grid-template-columns: repeat(3, minmax(200px, 1fr));
|
||||
}
|
||||
</style>
|
||||
|
|
@ -1,84 +1,84 @@
|
|||
import type { RequestHandler } from '@sveltejs/kit';
|
||||
import { boardGameApi } from '../_api';
|
||||
|
||||
export const get: RequestHandler = async ({ params }) => {
|
||||
const queryParams = {
|
||||
order_by: 'rank',
|
||||
ascending: 'false',
|
||||
limit: '10',
|
||||
}
|
||||
const response = await boardGameApi('get', `search`, queryParams);
|
||||
console.log('response', response);
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
body: {
|
||||
games: []
|
||||
}
|
||||
};
|
||||
}
|
||||
// export const get: RequestHandler = async ({ params }) => {
|
||||
// const queryParams = {
|
||||
// order_by: 'rank',
|
||||
// ascending: 'false',
|
||||
// limit: '10',
|
||||
// }
|
||||
// const response = await boardGameApi('get', `search`, queryParams);
|
||||
// console.log('response', response);
|
||||
// if (response.status === 404) {
|
||||
// // user hasn't created a todo list.
|
||||
// // start with an empty array
|
||||
// return {
|
||||
// body: {
|
||||
// games: []
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
if (response.status === 200) {
|
||||
const gameResponse = await response.json();
|
||||
const games = gameResponse?.games;
|
||||
console.log('games', games);
|
||||
return {
|
||||
body: {
|
||||
games: gameResponse?.games,
|
||||
}
|
||||
};
|
||||
}
|
||||
// if (response.status === 200) {
|
||||
// const gameResponse = await response.json();
|
||||
// const games = gameResponse?.games;
|
||||
// console.log('games', games);
|
||||
// return {
|
||||
// body: {
|
||||
// games: gameResponse?.games,
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
return {
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
// return {
|
||||
// status: response.status
|
||||
// };
|
||||
// }
|
||||
|
||||
export const post: RequestHandler = async ({ request }) => {
|
||||
const form = await request.formData();
|
||||
const minAge = form.get('minAge') || 0;
|
||||
console.log('minAge', minAge);
|
||||
const maxAge = form.get('maxAge') || 0;
|
||||
console.log('maxAge', maxAge);
|
||||
const minPlayers = form.get('minPlayers') || 1;
|
||||
console.log('minPlayers', minPlayers);
|
||||
const maxPlayers = form.get('maxPlayers') || 1;
|
||||
console.log('maxPlayers', maxPlayers);
|
||||
// export const post: RequestHandler = async ({ request }) => {
|
||||
// const form = await request.formData();
|
||||
// const minAge = form.get('minAge') || 0;
|
||||
// console.log('minAge', minAge);
|
||||
// const maxAge = form.get('maxAge') || 0;
|
||||
// console.log('maxAge', maxAge);
|
||||
// const minPlayers = form.get('minPlayers') || 1;
|
||||
// console.log('minPlayers', minPlayers);
|
||||
// const maxPlayers = form.get('maxPlayers') || 1;
|
||||
// console.log('maxPlayers', maxPlayers);
|
||||
|
||||
const queryParams = {
|
||||
order_by: 'rank',
|
||||
ascending: 'false',
|
||||
limit: '2',
|
||||
gt_min_players: String(+minPlayers === 1 ? 0 : +minPlayers - 1),
|
||||
lt_max_players: String(+maxPlayers + 1),
|
||||
gt_min_age: String(+minAge === 1 ? 0 : +minAge - 1),
|
||||
lt_max_age: String(+maxAge + 1),
|
||||
}
|
||||
const response = await boardGameApi('get', `search`, queryParams);
|
||||
console.log('response', response);
|
||||
if (response.status === 404) {
|
||||
// user hasn't created a todo list.
|
||||
// start with an empty array
|
||||
return {
|
||||
body: {
|
||||
games: []
|
||||
}
|
||||
};
|
||||
}
|
||||
// const queryParams = {
|
||||
// order_by: 'rank',
|
||||
// ascending: 'false',
|
||||
// limit: '1',
|
||||
// gt_min_players: String(+minPlayers === 1 ? 0 : +minPlayers - 1),
|
||||
// lt_max_players: String(+maxPlayers + 1),
|
||||
// gt_min_age: String(+minAge === 1 ? 0 : +minAge - 1),
|
||||
// lt_max_age: String(+maxAge + 1),
|
||||
// }
|
||||
// const response = await boardGameApi('get', `search`, queryParams);
|
||||
// console.log('response', response);
|
||||
// if (response.status === 404) {
|
||||
// // user hasn't created a todo list.
|
||||
// // start with an empty array
|
||||
// return {
|
||||
// body: {
|
||||
// games: []
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
if (response.status === 200) {
|
||||
const gameResponse = await response.json();
|
||||
const games = gameResponse?.games;
|
||||
console.log('games', games);
|
||||
return {
|
||||
body: {
|
||||
games: gameResponse?.games,
|
||||
}
|
||||
};
|
||||
}
|
||||
// if (response.status === 200) {
|
||||
// const gameResponse = await response.json();
|
||||
// const games = gameResponse?.games;
|
||||
// console.log('games', games);
|
||||
// return {
|
||||
// body: {
|
||||
// games,
|
||||
// }
|
||||
// };
|
||||
// }
|
||||
|
||||
return {
|
||||
status: response.status
|
||||
};
|
||||
}
|
||||
// return {
|
||||
// status: response.status
|
||||
// };
|
||||
// }
|
||||
Loading…
Reference in a new issue