Update external API, add HTML entity package to convert from external API, and update schema.

This commit is contained in:
Bradley Shellnut 2023-09-30 22:46:05 +13:00
parent 649c2dc74f
commit 2a640ffd90
8 changed files with 81 additions and 103 deletions

View file

@ -1,12 +1,14 @@
{
"name": "boredgame",
"version": "0.0.2",
"private": "true",
"scripts": {
"dev": "NODE_OPTIONS=\"--inspect\" vite dev --host",
"build": "prisma generate && vite build",
"package": "svelte-kit package",
"preview": "vite preview",
"test": "playwright test",
"test:ui": "svelte-kit sync && playwright test --ui",
"postinstall": "prisma generate",
"check": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json",
"check:watch": "svelte-kit sync && svelte-check --tsconfig ./tsconfig.json --watch",
@ -16,7 +18,9 @@
"site:update": "pnpm update -i -L",
"db:studio": "prisma studio",
"db:push": "prisma db push",
"db:seed": "prisma db seed"
"db:generate": "prisma generate",
"db:seed": "prisma db seed",
"i-changed-the-schema": "pnpm run db:push && pnpm run db:generate"
},
"prisma": {
"seed": "ts-node --esm prisma/seed.ts"
@ -65,7 +69,7 @@
},
"type": "module",
"engines": {
"node": ">=18.12.1",
"node": ">=18.0.0 <19.0.0 || >=20.0.0 <21.0.0",
"pnpm": ">=8"
},
"dependencies": {
@ -77,6 +81,7 @@
"@lucia-auth/adapter-prisma": "^3.0.1",
"@lukeed/uuid": "^2.0.1",
"@melt-ui/svelte": "^0.50.0",
"@paralleldrive/cuid2": "^2.2.2",
"@prisma/client": "5.3.1",
"@types/feather-icons": "^4.29.1",
"@vercel/og": "^0.5.13",
@ -87,6 +92,7 @@
"cookie": "^0.5.0",
"feather-icons": "^4.29.1",
"formsnap": "^0.0.9",
"html-entities": "^2.4.0",
"iconify-icon": "^1.0.8",
"just-kebab-case": "^4.2.0",
"loader": "^2.1.1",

View file

@ -29,6 +29,9 @@ dependencies:
'@melt-ui/svelte':
specifier: ^0.50.0
version: 0.50.0(svelte@4.2.1)
'@paralleldrive/cuid2':
specifier: ^2.2.2
version: 2.2.2
'@prisma/client':
specifier: 5.3.1
version: 5.3.1(prisma@5.3.1)
@ -59,6 +62,9 @@ dependencies:
formsnap:
specifier: ^0.0.9
version: 0.0.9(svelte@4.2.1)(sveltekit-superforms@1.7.2)(zod@3.22.2)
html-entities:
specifier: ^2.4.0
version: 2.4.0
iconify-icon:
specifier: ^1.0.8
version: 1.0.8
@ -1263,6 +1269,11 @@ packages:
nanoid: 4.0.2
svelte: 4.2.1
/@noble/hashes@1.3.2:
resolution: {integrity: sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==}
engines: {node: '>= 16'}
dev: false
/@nodelib/fs.scandir@2.1.5:
resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==}
engines: {node: '>= 8'}
@ -1281,6 +1292,12 @@ packages:
'@nodelib/fs.scandir': 2.1.5
fastq: 1.15.0
/@paralleldrive/cuid2@2.2.2:
resolution: {integrity: sha512-ZOBkgDwEdoYVlSeRbYYXs0S9MejQofiVYoTbKzy/6GQa39/q5tQU2IX46+shYnUkpEl3wc+J6wRlar7r2EK2xA==}
dependencies:
'@noble/hashes': 1.3.2
dev: false
/@playwright/test@1.37.0:
resolution: {integrity: sha512-181WBLk4SRUyH1Q96VZl7BP6HcK0b7lbdeKisn3N/vnjitk+9HbdlFz/L5fey05vxaAhldIDnzo8KUoy8S3mmQ==}
engines: {node: '>=16'}
@ -2825,6 +2842,10 @@ packages:
resolution: {integrity: sha512-Ox1pJVrDCyGHMG9CFg1tmrRUMRPRsAWYc/PinY0XzJU4K7y7vjNoLKIQ7BR5UJMCxNN8EM1MNDmHWA/B3aZUuw==}
engines: {node: '>=6'}
/html-entities@2.4.0:
resolution: {integrity: sha512-igBTJcNNNhvZFRtm8uA6xMY6xYleeDwn3PeBCkDz7tHttv4F2hsDI2aPgNERWzvRcNYHNT3ymRaQzllmXj4YsQ==}
dev: false
/https-proxy-agent@5.0.1:
resolution: {integrity: sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==}
engines: {node: '>= 6'}

View file

@ -72,16 +72,16 @@ model Key {
id String @id @unique
hashed_password String?
user_id String
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
user User @relation(references: [id], fields: [user_id], onDelete: Cascade)
@@index([user_id])
@@map("keys")
}
model Collection {
id String @id @default(cuid())
user_id String @unique
user User @relation(references: [id], fields: [user_id])
id String @id @default(cuid())
user_id String @unique
user User @relation(references: [id], fields: [user_id])
items CollectionItem[]
@@index([user_id])
@ -162,6 +162,7 @@ model Game {
year_published Int? @db.Year
min_players Int?
max_players Int?
playtime Int?
min_playtime Int?
max_playtime Int?
min_age Int?
@ -169,6 +170,7 @@ model Game {
thumb_url String?
url String?
rules_url String?
is_expansion Boolean @default(false)
primary_publisher_id String?
primary_publisher Publisher? @relation("PrimaryPublishers", references: [id], fields: [primary_publisher_id])
primary_designer_id String?
@ -206,14 +208,14 @@ model GameName {
}
model Publisher {
id String @id @default(cuid())
name String
slug String
external_id String @unique
games Game[]
primary_publisher Game[] @relation("PrimaryPublishers")
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
id String @id @default(cuid())
name String
slug String
external_id String @unique
games Game[]
primary_publisher Game[] @relation("PrimaryPublishers")
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("publishers")
@ -233,13 +235,13 @@ model Category {
}
model Mechanic {
id String @id @default(cuid())
name String
slug String
games Game[]
external_id String @unique
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
id String @id @default(cuid())
name String
slug String
games Game[]
external_id String @unique
created_at DateTime @default(now()) @db.Timestamp(6)
updated_at DateTime @updatedAt @db.Timestamp(6)
@@fulltext([name])
@@map("mechanics")
@ -275,7 +277,7 @@ model Expansion {
id String @id @default(cuid())
name String
year_published Int?
baseGame Game? @relation(fields: [base_game_id], references: [id])
base_game Game? @relation(fields: [base_game_id], references: [id])
base_game_id String?
external_id String @unique
created_at DateTime @default(now()) @db.Timestamp(6)

View file

@ -27,7 +27,7 @@ async function main() {
await prisma.mechanic.create({
data: {
name: mechanic.name,
external_id: mechanic.id,
external_id: createId(),
slug: kebabCase(mechanic.name)
}
});
@ -42,7 +42,7 @@ async function main() {
await prisma.category.create({
data: {
name: category.name,
external_id: category.id,
external_id: createId(),
slug: kebabCase(category.name)
}
});

View file

@ -135,16 +135,13 @@ export type GameType = {
};
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;
@ -164,23 +161,11 @@ export type SearchQuery = {
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;
};

View file

@ -7,6 +7,8 @@ import type { GameType, SearchQuery } from '$lib/types';
import { mapAPIGameToBoredGame } from '$lib/utils/gameMapper.js';
import { search_schema } from '$lib/zodValidation';
import type { PageServerLoad } from '../$types.js';
import { BggClient } from 'boardgamegeekclient';
import type { BggThingDto } from 'boardgamegeekclient/dist/esm/dto/index.js';
// import { listGameSchema } from '$lib/config/zod-schemas.js';
/**
@ -78,29 +80,28 @@ async function searchForGames(urlQueryParams: SearchQuery, eventFetch) {
console.log('games from DB', games);
let totalCount = games?.length || 0;
if (!games || games.length === 0) {
const url = new URL(
`https://api.boardgameatlas.com/api/search${urlQueryParams ? `?${urlQueryParams}` : ''}`
if (totalCount === 0) {
console.log('No games found in DB for', urlQueryParams.get('name'));
const externalResponse = await eventFetch(
`/api/external/search${urlQueryParams ? `?${urlQueryParams}` : ''}`,
requestInit
);
const headers: HeadersInit = new Headers();
headers.set('Content-Type', 'application/json');
const requestInit: RequestInit = {
method: 'GET',
headers
};
const response = await fetch(url, requestInit);
console.log('Back from external search', externalResponse);
if (!response.ok) {
console.log('Status not 200', response.status);
throw error(response.status);
}
// const games: GameType[] = [];
// let totalCount = 0;
if (response.ok) {
const gameResponse = await response.json();
const gameList: GameType[] = gameResponse?.games;
totalCount = gameResponse?.count;
// // const games: GameType[] = [];
// // let totalCount = 0;
if (externalResponse.ok) {
const gameResponse = await externalResponse.json();
console.log('response from external api', gameResponse);
const gameList: BggThingDto[] = gameResponse?.games;
totalCount = gameResponse?.totalCount;
console.log('totalCount', totalCount);
gameList.forEach((game) => {
if (game?.min_players && game?.max_players) {
@ -229,8 +230,8 @@ export const load: PageServerLoad = async ({ params, locals, request, fetch, url
console.log('searchParams', searchParams);
searchParams.limit = searchParams.limit || `${defaults.limit}`;
searchParams.skip = searchParams.skip || `${defaults.skip}`;
searchParams.order = searchParams.order || 'asc';
searchParams.sort = searchParams.sort || 'name';
searchParams.order = searchParams.order || defaults.order;
searchParams.sort = searchParams.sort || defaults.sort;
const form = await superValidate(searchParams, search_schema);
// const modifyListForm = await superValidate(listGameSchema);
@ -239,8 +240,6 @@ export const load: PageServerLoad = async ({ params, locals, request, fetch, url
ascending: false,
limit: form.data?.limit,
skip: form.data?.skip,
client_id: BOARD_GAME_ATLAS_CLIENT_ID,
fuzzy_match: true,
name: form.data?.q
};

View file

@ -6,7 +6,8 @@ import { search_schema } from '$lib/zodValidation.js';
export async function GET({ url, locals, params }) {
const searchParams = Object.fromEntries(url.searchParams);
const q = searchParams?.q || '';
console.log('searchParams external', searchParams);
const name = searchParams?.name || '';
const exact = parseInt(searchParams.exact) || 0;
const limit = parseInt(searchParams?.limit) || 10;
const skip = parseInt(searchParams?.skip) || 0;
@ -14,7 +15,7 @@ export async function GET({ url, locals, params }) {
// TODO: Debounce and throttle
try {
search_schema.parse({
q,
q: name,
limit,
skip
});
@ -29,7 +30,7 @@ export async function GET({ url, locals, params }) {
const client = BggClient.Create();
const request: ISearchRequest = {
query: q,
query: name,
exact,
type: ['boardgame', 'boardgameaccessory', 'boardgameexpansion']
};
@ -46,7 +47,11 @@ export async function GET({ url, locals, params }) {
if (end > result.total) {
end = result.total;
}
const apiResponse = result.items.slice(start, end);
const games = result.items.slice(start, end);
const apiResponse = {
totalCount: response[0].total,
games
};
console.log('Response from BGG', JSON.stringify(result, null, 2));

View file

@ -1,56 +1,16 @@
import { error, json } from '@sveltejs/kit';
import { Prisma } from '@prisma/client';
import z from 'zod';
import prisma from '$lib/prisma.js';
import { superValidate } from 'sveltekit-superforms/server';
import { search_schema } from '$lib/zodValidation.js';
// Search a user's collection
export const GET = async ({ url, locals, params, request }) => {
// try {
// z.parse;
// } catch (e) {
// console.error(e);
// return error(500, { message: 'Something went wrong' });
// }
// let games = await prisma.game.findMany({
// where: {
// name: {
// search: urlQueryParams?.name
// },
// min_players: {
// gte: urlQueryParams?.min_players || 0
// },
// max_players: {
// lte: urlQueryParams?.max_players || 100
// },
// min_playtime: {
// gte: urlQueryParams?.min_playtime || 0
// },
// max_playtime: {
// lte: urlQueryParams?.max_playtime || 5000
// },
// min_age: {
// gte: urlQueryParams?.min_age || 0
// }
// },
// skip: urlQueryParams?.skip,
// take: urlQueryParams?.limit,
// orderBy: {
// name: 'asc'
// }
// });
const searchParams = Object.fromEntries(url.searchParams);
const q = searchParams?.q || '';
const limit = parseInt(searchParams?.limit) || 10;
const skip = parseInt(searchParams?.skip) || 0;
const order: Prisma.SortOrder = <Prisma.SortOrder>searchParams?.order || 'asc';
const sort = searchParams?.sort || 'name';
// const session = await locals.auth.validate();
console.log('url', url);
// console.log('username', locals?.user?.id);
try {
const orderBy = { [sort]: order };