From e69ed91e2c098e13be9445bb8eeb90b0ddff654a Mon Sep 17 00:00:00 2001 From: Bradley Shellnut Date: Fri, 29 Sep 2023 21:06:46 +1300 Subject: [PATCH] Updating MeltUI and adding APIs for external searching games on BGG. --- package.json | 5 +- pnpm-lock.yaml | 116 ++++++++++++++++--- src/routes/api/external/game/[id]/+server.ts | 43 +++++++ src/routes/api/external/search/+server.ts | 57 +++++++++ 4 files changed, 205 insertions(+), 16 deletions(-) create mode 100644 src/routes/api/external/game/[id]/+server.ts create mode 100644 src/routes/api/external/search/+server.ts diff --git a/package.json b/package.json index 2ccb54c..585c52b 100644 --- a/package.json +++ b/package.json @@ -73,14 +73,15 @@ "@fontsource/fira-mono": "^4.5.10", "@iconify-icons/line-md": "^1.2.26", "@iconify-icons/mdi": "^1.2.47", - "@lucia-auth/adapter-mysql": "^2.0.0", + "@lucia-auth/adapter-mysql": "^2.1.0", "@lucia-auth/adapter-prisma": "^3.0.1", "@lukeed/uuid": "^2.0.1", - "@melt-ui/svelte": "^0.37.6", + "@melt-ui/svelte": "^0.50.0", "@prisma/client": "5.3.1", "@types/feather-icons": "^4.29.1", "@vercel/og": "^0.5.13", "bits-ui": "^0.0.27", + "boardgamegeekclient": "^1.9.1", "class-variance-authority": "^0.6.1", "clsx": "^1.2.1", "cookie": "^0.5.0", diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fb4675a..32f9d1b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -18,8 +18,8 @@ dependencies: specifier: ^1.2.47 version: 1.2.47 '@lucia-auth/adapter-mysql': - specifier: ^2.0.0 - version: 2.0.0(lucia@2.7.0) + specifier: ^2.1.0 + version: 2.1.0(lucia@2.7.0) '@lucia-auth/adapter-prisma': specifier: ^3.0.1 version: 3.0.1(@prisma/client@5.3.1)(lucia@2.7.0) @@ -27,8 +27,8 @@ dependencies: specifier: ^2.0.1 version: 2.0.1 '@melt-ui/svelte': - specifier: ^0.37.6 - version: 0.37.6(svelte@4.2.1) + specifier: ^0.50.0 + version: 0.50.0(svelte@4.2.1) '@prisma/client': specifier: 5.3.1 version: 5.3.1(prisma@5.3.1) @@ -41,6 +41,9 @@ dependencies: bits-ui: specifier: ^0.0.27 version: 0.0.27(@sveltejs/kit@1.25.1)(svelte@4.2.1) + boardgamegeekclient: + specifier: ^1.9.1 + version: 1.9.1 class-variance-authority: specifier: ^0.6.1 version: 0.6.1 @@ -99,7 +102,7 @@ dependencies: devDependencies: '@melt-ui/pp': specifier: ^0.1.2 - version: 0.1.2(@melt-ui/svelte@0.37.6)(svelte@4.2.1) + version: 0.1.2(@melt-ui/svelte@0.50.0)(svelte@4.2.1) '@playwright/test': specifier: ^1.37.0 version: 1.37.0 @@ -1063,15 +1066,32 @@ packages: resolution: {integrity: sha512-jk3WqquEJRlcyu7997NtR5PibI+y5bi+LS3hPmguVClypenMsCY3CBa3LAQnozRCtCrYWSEtAdiskpamuJRFOQ==} dependencies: '@floating-ui/utils': 0.1.1 + dev: false + + /@floating-ui/core@1.5.0: + resolution: {integrity: sha512-kK1h4m36DQ0UHGj5Ah4db7R0rHemTqqO0QLvUqi1/mUUp3LuAWbWxdxSIf/XsnH9VS6rRVPLJCncjRzUvyCLXg==} + dependencies: + '@floating-ui/utils': 0.1.4 /@floating-ui/dom@1.5.1: resolution: {integrity: sha512-KwvVcPSXg6mQygvA1TjbN/gh///36kKtllIF8SUm0qpFj8+rvYrpvlYdL1JoA71SHpDqgSSdGOSoQ0Mp3uY5aw==} dependencies: '@floating-ui/core': 1.4.1 '@floating-ui/utils': 0.1.1 + dev: false + + /@floating-ui/dom@1.5.3: + resolution: {integrity: sha512-ClAbQnEqJAKCJOEbbLo5IUlZHkNszqhuxS4fHAVxRPXPya6Ysf2G8KypnYcOTpx6I8xcgF9bbHb6g/2KpbV8qA==} + dependencies: + '@floating-ui/core': 1.5.0 + '@floating-ui/utils': 0.1.4 /@floating-ui/utils@0.1.1: resolution: {integrity: sha512-m0G6wlnhm/AX0H12IOWtK8gASEMffnX08RtKkCgTdHb9JpHKGloI7icFfLg9ZmQeavcvR0PKmzxClyuFPSjKWw==} + dev: false + + /@floating-ui/utils@0.1.4: + resolution: {integrity: sha512-qprfWkn82Iw821mcKofJ5Pk9wgioHicxcQMxx+5zt5GSKoqdWvgG5AxVmpmUUjzTLPVSH5auBrhI93Deayn/DA==} /@fontsource/fira-mono@4.5.10: resolution: {integrity: sha512-bxUnRP8xptGRo8YXeY073DSpfK74XpSb0ZyRNpHV9WvLnJ7TwPOjZll8hTMin7zLC6iOp59pDZ8EQDj1gzgAQQ==} @@ -1154,8 +1174,8 @@ packages: '@jridgewell/resolve-uri': 3.1.0 '@jridgewell/sourcemap-codec': 1.4.15 - /@lucia-auth/adapter-mysql@2.0.0(lucia@2.7.0): - resolution: {integrity: sha512-8a4JZ3VjDyRu/mAop2hEt/jOJO2HXwWIAid6a4wGiR8wgnlyOws9brRc+/wxQHSOlWUlrWemrfDvXLs5mMtkeQ==} + /@lucia-auth/adapter-mysql@2.1.0(lucia@2.7.0): + resolution: {integrity: sha512-LkqsJHQS9KuMs+cTJQJnaqb6obqMyJfblyyLM0Ogoimzikb5orbkWI0C8eY3Supr60bgP4PYuQxYZm8xxera0Q==} peerDependencies: '@planetscale/database': ^1.0.0 lucia: ^2.0.0 @@ -1208,14 +1228,14 @@ packages: - encoding - supports-color - /@melt-ui/pp@0.1.2(@melt-ui/svelte@0.37.6)(svelte@4.2.1): + /@melt-ui/pp@0.1.2(@melt-ui/svelte@0.50.0)(svelte@4.2.1): resolution: {integrity: sha512-GZeqp7UWLNZUC2dJpREnZrWMR88vy27WO7C3cIBz4KW3/CFD19FjNkd3VbSRfcRryrMkdnEs9nu2VUa8/0u58w==} engines: {pnpm: '>=8.6.3'} peerDependencies: '@melt-ui/svelte': '>= 0.29.0' svelte: ^3.55.0 || ^4.0.0 dependencies: - '@melt-ui/svelte': 0.37.6(svelte@4.2.1) + '@melt-ui/svelte': 0.50.0(svelte@4.2.1) svelte: 4.2.1 dev: true @@ -1231,14 +1251,15 @@ packages: svelte: 4.2.1 dev: false - /@melt-ui/svelte@0.37.6(svelte@4.2.1): - resolution: {integrity: sha512-rh3d1FN9JLOCZkrc9x3sOPjWy4wIUMlFhouy+u1sBAcVSYyVKOx7g8vV4O75PQRmgpWEWbQV9yCOa3jj3Gur3A==} + /@melt-ui/svelte@0.50.0(svelte@4.2.1): + resolution: {integrity: sha512-NcWwxwStXq77/yOuBfnGkuJdUta3M4SwqZECdaRpAQ61BHI3qz7WW2ZM42JmDvGSs9W6ww2kZFNF8XNTO92CdA==} peerDependencies: svelte: '>=3 <5' dependencies: - '@floating-ui/core': 1.4.1 - '@floating-ui/dom': 1.5.1 - focus-trap: 7.5.2 + '@floating-ui/core': 1.5.0 + '@floating-ui/dom': 1.5.3 + dequal: 2.0.3 + focus-trap: 7.5.3 nanoid: 4.0.2 svelte: 4.2.1 @@ -1969,6 +1990,17 @@ packages: - supports-color dev: false + /boardgamegeekclient@1.9.1: + resolution: {integrity: sha512-sSL27GGYHBp7PEt/j3NyrCpkMWgtu0I0VxYveveU5l5POnBhlSlyESy3rJe/qvHXx55DSE3VuDNeDzHHuIjLHA==} + engines: {node: '>=10'} + dependencies: + fast-xml-parser: 3.21.1 + isomorphic-unfetch: 3.1.0 + jackson-js: 1.1.0 + transitivePeerDependencies: + - encoding + dev: false + /brace-expansion@1.1.11: resolution: {integrity: sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==} dependencies: @@ -2558,6 +2590,13 @@ packages: resolution: {integrity: sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==} dev: true + /fast-xml-parser@3.21.1: + resolution: {integrity: sha512-FTFVjYoBOZTJekiUsawGsSYV9QL0A+zDYCRj7y34IO6Jg+2IMYEtQa+bbictpdpV8dHxXywqU7C0gRDEOFtBFg==} + hasBin: true + dependencies: + strnum: 1.0.5 + dev: false + /fastq@1.15.0: resolution: {integrity: sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==} dependencies: @@ -2617,6 +2656,12 @@ packages: resolution: {integrity: sha512-p6vGNNWLDGwJCiEjkSK6oERj/hEyI9ITsSwIUICBoKLlWiTWXJRfQibCwcoi50rTZdbi87qDtUlMCmQwsGSgPw==} dependencies: tabbable: 6.2.0 + dev: false + + /focus-trap@7.5.3: + resolution: {integrity: sha512-7UsT/eSJcTPF0aZp73u7hBRTABz26knRRTJfoTGFCQD5mUImLIIOwWWCrtoQdmWa7dykBi6H+Cp5i3S/kvsMeA==} + dependencies: + tabbable: 6.2.0 /follow-redirects@1.15.2: resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} @@ -2882,6 +2927,24 @@ packages: resolution: {integrity: sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==} dev: true + /isomorphic-unfetch@3.1.0: + resolution: {integrity: sha512-geDJjpoZ8N0kWexiwkX8F9NkTsXhetLPVbZFQ+JTW239QNOwvB0gniuR1Wc6f0AMTn7/mFGyXvHTifrCp/GH8Q==} + dependencies: + node-fetch: 2.6.7 + unfetch: 4.2.0 + transitivePeerDependencies: + - encoding + dev: false + + /jackson-js@1.1.0: + resolution: {integrity: sha512-EDKwt9U6dd/zNW/7p3VejSYUYgUkZ5c7v4gGAO7KhA1qLp6DXPpBjIx9ELI9LzQjpFtoL8g6CAQAhBuDmzkYMw==} + dependencies: + lodash.clone: 4.5.0 + lodash.clonedeep: 4.5.0 + meriyah: 1.9.15 + reflect-metadata: 0.1.13 + dev: false + /jiti@1.18.2: resolution: {integrity: sha512-QAdOptna2NYiSSpv0O/BwoHBSmz4YhpzJHyi+fnMRTXFjp7B8i/YG5Z8IfusxB1ufjcD2Sre1F3R+nX3fvy7gg==} hasBin: true @@ -2969,6 +3032,14 @@ packages: p-locate: 5.0.0 dev: true + /lodash.clone@4.5.0: + resolution: {integrity: sha512-GhrVeweiTD6uTmmn5hV/lzgCQhccwReIVRLHp7LT4SopOjqEZ5BbX8b5WWEtAKasjmy8hR7ZPwsYlxRCku5odg==} + dev: false + + /lodash.clonedeep@4.5.0: + resolution: {integrity: sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==} + dev: false + /lodash.merge@4.6.2: resolution: {integrity: sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==} dev: true @@ -3037,6 +3108,11 @@ packages: resolution: {integrity: sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==} engines: {node: '>= 8'} + /meriyah@1.9.15: + resolution: {integrity: sha512-D4rT6XIYGqZab0EI/xbihUpwh0WbNRVQ35l2J/5QC2atxaI8h3KvA55DEJLBB/FRdaji7JwkNehfCRjCyjCjqw==} + engines: {node: '>=6.0.0'} + dev: false + /micromatch@4.0.5: resolution: {integrity: sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==} engines: {node: '>=8.6'} @@ -3799,6 +3875,10 @@ packages: dependencies: picomatch: 2.3.1 + /reflect-metadata@0.1.13: + resolution: {integrity: sha512-Ts1Y/anZELhSsjMcU605fU9RE4Oi3p5ORujwbIKXfWa+0Zxs510Qrmrce5/Jowq3cHSZSJqBjypxmHarc+vEWg==} + dev: false + /regenerator-runtime@0.13.11: resolution: {integrity: sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==} dev: false @@ -4051,6 +4131,10 @@ packages: acorn: 8.10.0 dev: true + /strnum@1.0.5: + resolution: {integrity: sha512-J8bbNyKKXl5qYcR36TIO8W3mVGVHrmmxsd5PAItGkmyzwJvybiw2IVq5nqd0i4LSNSkB/sx9VHllbfFdr9k1JA==} + dev: false + /sucrase@3.32.0: resolution: {integrity: sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==} engines: {node: '>=8'} @@ -4467,6 +4551,10 @@ packages: dependencies: busboy: 1.6.0 + /unfetch@4.2.0: + resolution: {integrity: sha512-F9p7yYCn6cIW9El1zi0HI6vqpeIvBsr3dSuRO6Xuppb1u5rXpCPmMvLSyECLhybr9isec8Ohl0hPekMVrEinDA==} + dev: false + /unicode-trie@2.0.0: resolution: {integrity: sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==} dependencies: diff --git a/src/routes/api/external/game/[id]/+server.ts b/src/routes/api/external/game/[id]/+server.ts new file mode 100644 index 0000000..d8c0f73 --- /dev/null +++ b/src/routes/api/external/game/[id]/+server.ts @@ -0,0 +1,43 @@ +import { HttpError_1, error, json } from '@sveltejs/kit'; +import { BggClient } from 'boardgamegeekclient'; + +export async function GET({ url, locals, params }) { + const game_id = Number(params.id).valueOf(); + + // TODO: Debounce excessive calls and possibly throttle + if (isNaN(game_id) || !isFinite(game_id)) { + throw error(400, { message: 'Invalid game id' }); + } + + console.log('Searching for', game_id); + const client = BggClient.Create(); + const response = await client.thing.query({ + id: game_id + }); + + if (!response || response.length === 0) { + throw error(404, { message: 'No results found in external search' }); + } + + const result = response[0]; + + const apiResponse = { + external_id: result.id, + name: result.name, + type: result.type, + description: result.description, + thumbnail: result.thumbnail, + image: result.image, + year_published: result.yearpublished, + min_players: result.minplayers, + max_players: result.maxplayers, + playing_time: result.playingtime, + min_playtime: result.minplaytime, + max_playtime: result.maxplaytime, + min_age: result.minage + }; + + console.log('Response from BGG', JSON.stringify(result, null, 2)); + + return json(apiResponse); +} diff --git a/src/routes/api/external/search/+server.ts b/src/routes/api/external/search/+server.ts new file mode 100644 index 0000000..602bdf7 --- /dev/null +++ b/src/routes/api/external/search/+server.ts @@ -0,0 +1,57 @@ +import { error, json } from '@sveltejs/kit'; +import { ZodError } from 'zod'; +import { BggClient } from 'boardgamegeekclient'; +import type { ISearchRequest } from 'boardgamegeekclient/dist/esm/request/index.js'; +import { search_schema } from '$lib/zodValidation.js'; + +export async function GET({ url, locals, params }) { + const searchParams = Object.fromEntries(url.searchParams); + const q = searchParams?.q || ''; + const exact = parseInt(searchParams.exact) || 0; + const limit = parseInt(searchParams?.limit) || 10; + const skip = parseInt(searchParams?.skip) || 0; + console.log('exact', exact); + console.log('limit', limit); + console.log('skip', skip); + + // TODO: Debounce and throttle + try { + search_schema.parse({ + q, + limit, + skip + }); + } catch (e) { + console.error(e); + if (e instanceof ZodError) { + throw error(400, { message: e.flatten().fieldErrors }); + } + + throw error(500, { message: 'Something went wrong' }); + } + + const client = BggClient.Create(); + const request: ISearchRequest = { + query: q, + exact, + type: ['boardgame', 'boardgameaccessory', 'boardgameexpansion'] + }; + const response = await client.search.query(request); + + if (!response || response.length === 0 || response[0]?.total === 0) { + throw error(404, { message: 'No results found in external search' }); + } + + const result = response[0]; + const start = skip; + let end = start + limit; + + if (end > result.total) { + end = result.total; + } + const apiResponse = result.items.slice(start, end); + + console.log('Response from BGG', JSON.stringify(result, null, 2)); + + return json(apiResponse); +}