From 2a640ffd9073530853889de3368f82b6659848e5 Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Sat, 30 Sep 2023 22:46:05 +1300 Subject: [PATCH] Update external API, add HTML entity package to convert from external API, and update schema. --- package.json | 10 ++++-- pnpm-lock.yaml | 21 ++++++++++++ prisma/schema.prisma | 42 ++++++++++++----------- prisma/seed.ts | 4 +-- src/lib/types.ts | 15 -------- src/routes/(app)/search/+page.server.ts | 39 ++++++++++----------- src/routes/api/external/search/+server.ts | 13 ++++--- src/routes/api/game/search/+server.ts | 40 --------------------- 8 files changed, 81 insertions(+), 103 deletions(-) diff --git a/package.json b/package.json index 585c52b..15d2ec4 100644 --- a/package.json +++ b/package.json @@ -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", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 32f9d1b..1509138 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -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'} diff --git a/prisma/schema.prisma b/prisma/schema.prisma index 7deb2d3..d90a0ce 100644 --- a/prisma/schema.prisma +++ b/prisma/schema.prisma @@ -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) diff --git a/prisma/seed.ts b/prisma/seed.ts index 561ba40..acb1467 100644 --- a/prisma/seed.ts +++ b/prisma/seed.ts @@ -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) } }); diff --git a/src/lib/types.ts b/src/lib/types.ts index a01ddf9..1657896 100644 --- a/src/lib/types.ts +++ b/src/lib/types.ts @@ -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; }; diff --git a/src/routes/(app)/search/+page.server.ts b/src/routes/(app)/search/+page.server.ts index 6ce0c87..56bdeef 100644 --- a/src/routes/(app)/search/+page.server.ts +++ b/src/routes/(app)/search/+page.server.ts @@ -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 }; diff --git a/src/routes/api/external/search/+server.ts b/src/routes/api/external/search/+server.ts index 77d6076..4b48ed5 100644 --- a/src/routes/api/external/search/+server.ts +++ b/src/routes/api/external/search/+server.ts @@ -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)); diff --git a/src/routes/api/game/search/+server.ts b/src/routes/api/game/search/+server.ts index 77200f0..7db5220 100644 --- a/src/routes/api/game/search/+server.ts +++ b/src/routes/api/game/search/+server.ts @@ -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 = 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 };