diff --git a/src/lib/types.ts b/src/lib/types.ts
index 1657896..86caf89 100644
--- a/src/lib/types.ts
+++ b/src/lib/types.ts
@@ -93,18 +93,37 @@ export type CategoryType = {
export type PublisherType = {
id: string;
- name: string;
};
export type DesignerType = {
id: string;
- name: string;
+};
+
+export type ArtistType = {
+ id: string;
+}
+
+export type ExpansionType = {
+ id: string;
+}
+
+export type BGGLinkType =
+ | 'boardgamecategory'
+ | 'boardgamemechanic'
+ | 'boardgameexpansion'
+ | 'boardgameartist'
+ | 'boardgamepublisher';
+
+export type BGGLink = {
+ id: number;
+ type: BGGLinkType;
+ value: string;
};
export type GameType = {
id: string;
- handle: string;
name: string;
+ slug: string;
url: string;
edit_url: string;
thumb_url: string;
@@ -122,16 +141,17 @@ export type GameType = {
primary_designer: DesignerType;
designers: DesignerType[];
developers: String[];
- artists: String[];
+ artists: ArtistType[];
+ expansions: ExpansionType[];
min_players: number;
max_players: number;
min_playtime: number;
max_playtime: number;
min_age: number;
description: string;
- description_preview: string;
players: string;
- playtime: string;
+ playtime: number;
+ external_id: number;
};
export type SearchQuery = {
@@ -140,7 +160,7 @@ export type SearchQuery = {
ids?: string[];
list_id?: string;
random?: boolean;
- name?: string;
+ q?: string;
exact?: boolean;
designer?: string;
publisher?: string;
diff --git a/src/lib/utils/dbUtils.ts b/src/lib/utils/dbUtils.ts
new file mode 100644
index 0000000..8940c95
--- /dev/null
+++ b/src/lib/utils/dbUtils.ts
@@ -0,0 +1,415 @@
+import prisma from "$lib/prisma";
+import type { GameType } from "$lib/types";
+import type { Game } from "@prisma/client";
+import type { BggThingDto } from "boardgamegeekclient/dist/esm/dto";
+import type { BggLinkDto } from "boardgamegeekclient/dist/esm/dto/concrete/subdto";
+import kebabCase from "just-kebab-case";
+import { mapAPIGameToBoredGame } from "./gameMapper";
+
+export async function createArtist(externalArtist: BggLinkDto) {
+ try {
+ let dbArtist = await prisma.artist.findFirst({
+ where: {
+ external_id: externalArtist.id
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+ if (dbArtist) {
+ return dbArtist;
+ }
+ console.log('Creating artist', JSON.stringify(externalArtist, null, 2));
+ let artist = await prisma.artist.create({
+ data: {
+ name: externalArtist.value,
+ external_id: externalArtist.id,
+ slug: kebabCase(externalArtist.value)
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+
+ console.log('Created artist', JSON.stringify(artist, null, 2));
+ return artist;
+ } catch (e) {
+ console.error(e);
+ throw new Error('Something went wrong creating Artist');
+ }
+}
+
+export async function createDesigner(externalDesigner: BggLinkDto) {
+ try {
+ let dbDesigner = await prisma.designer.findFirst({
+ where: {
+ external_id: externalDesigner.id
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+ if (dbDesigner) {
+ return dbDesigner;
+ }
+ console.log('Creating designer', JSON.stringify(externalDesigner, null, 2));
+ let designer = await prisma.designer.create({
+ data: {
+ name: externalDesigner.value,
+ external_id: externalDesigner.id,
+ slug: kebabCase(externalDesigner.value)
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+
+ console.log('Created designer', JSON.stringify(designer, null, 2));
+ return designer;
+ } catch (e) {
+ console.error(e);
+ throw new Error('Something went wrong creating Designer');
+ }
+}
+
+export async function createPublisher(externalPublisher: BggLinkDto) {
+ try {
+ let dbPublisher = await prisma.publisher.findFirst({
+ where: {
+ external_id: externalPublisher.id
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+ if (dbPublisher) {
+ return dbPublisher;
+ }
+ console.log('Creating publisher', JSON.stringify(externalPublisher, null, 2));
+ let publisher = await prisma.publisher.create({
+ data: {
+ name: externalPublisher.value,
+ external_id: externalPublisher.id,
+ slug: kebabCase(externalPublisher.value)
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+
+ console.log('Created publisher', JSON.stringify(publisher, null, 2));
+ return publisher;
+ } catch (e) {
+ console.error(e);
+ throw new Error('Something went wrong creating Publisher');
+ }
+}
+
+export async function createCategory(externalCategory: BggLinkDto) {
+ try {
+ let dbCategory = await prisma.category.findFirst({
+ where: {
+ external_id: externalCategory.id
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+ if (dbCategory) {
+ return dbCategory;
+ }
+ console.log('Creating category', JSON.stringify(externalCategory, null, 2));
+ let category = await prisma.category.create({
+ data: {
+ name: externalCategory.value,
+ external_id: externalCategory.id,
+ slug: kebabCase(externalCategory.value)
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+
+ console.log('Created category', JSON.stringify(category, null, 2));
+
+ return category;
+ } catch (e) {
+ console.error(e);
+ throw new Error('Something went wrong creating Category');
+ }
+}
+
+export async function createMechanic(externalMechanic: BggLinkDto) {
+ try {
+ let dbMechanic = await prisma.mechanic.findFirst({
+ where: {
+ external_id: externalMechanic.id
+ },
+ select: {
+ id: true,
+ name: true,
+ slug: true,
+ external_id: true
+ }
+ });
+ if (dbMechanic) {
+ return dbMechanic;
+ }
+ console.log('Creating mechanic', JSON.stringify(externalMechanic, null, 2));
+ let mechanic = await prisma.mechanic.upsert({
+ where: {
+ external_id: externalMechanic.id
+ },
+ create: {
+ name: externalMechanic.value,
+ external_id: externalMechanic.id,
+ slug: kebabCase(externalMechanic.value)
+ },
+ update: {
+ name: externalMechanic.value,
+ slug: kebabCase(externalMechanic.value)
+ }
+ });
+
+ console.log('Created mechanic', JSON.stringify(mechanic, null, 2));
+
+ return mechanic;
+ } catch (e) {
+ console.error(e);
+ throw new Error('Something went wrong creating Mechanic');
+ }
+}
+
+export async function createExpansion(game: Game, externalExpansion: BggLinkDto, gameIsExpansion: boolean, eventFetch: Function) {
+ try {
+ let dbExpansionGame = await prisma.game.findUnique({
+ where: {
+ external_id: externalExpansion.id
+ }
+ });
+
+ if (!dbExpansionGame) {
+ const externalGameResponse = await eventFetch(
+ `/api/external/game/${externalExpansion.id}?simplified=true`
+ );
+ if (externalGameResponse.ok) {
+ const externalGame = await externalGameResponse.json();
+ console.log('externalGame', externalGame);
+ let boredGame = mapAPIGameToBoredGame(externalGame);
+ dbExpansionGame = await createOrUpdateGameMinimal(boredGame);
+ } else {
+ throw new Error(`${gameIsExpansion ? 'Base game' : 'Expansion game'} not found and failed to create.`);
+ }
+ }
+
+ let dbExpansion;
+ let baseGameId;
+ let gameId;
+ if (gameIsExpansion) {
+ console.log('External expansion is expansion. Looking for base game', JSON.stringify(game, null, 2));
+ dbExpansion = await prisma.expansion.findFirst({
+ where: {
+ game_id: dbExpansionGame.id
+ },
+ select: {
+ id: true,
+ base_game_id: true,
+ game_id: true
+ }
+ });
+ baseGameId = game.id;
+ gameId = dbExpansionGame.id;
+ } else {
+ console.log('External Expansion is base game. Looking for expansion', JSON.stringify(game, null, 2));
+ dbExpansion = await prisma.expansion.findFirst({
+ where: {
+ base_game_id: dbExpansionGame.id
+ },
+ select: {
+ id: true,
+ base_game_id: true,
+ game_id: true
+ }
+ });
+ baseGameId = dbExpansionGame.id;
+ gameId = game.id;
+ }
+
+ if (dbExpansion) {
+ console.log('Expansion already exists', JSON.stringify(dbExpansion, null, 2));
+ return dbExpansion;
+ }
+
+ console.log(`Creating expansion. baseGameId: ${baseGameId}, gameId: ${gameId}`);
+ let expansion = await prisma.expansion.create({
+ data: {
+ base_game_id: baseGameId,
+ game_id: gameId
+ }
+ });
+
+ console.log('Created expansion', JSON.stringify(expansion, null, 2));
+
+ return expansion;
+ } catch (e) {
+ console.error(e);
+ throw new Error('Something went wrong creating Expansion');
+ }
+}
+
+export async function createOrUpdateGameMinimal(game: GameType) {
+ console.log('Creating or updating minimal game data', JSON.stringify(game, null, 2));
+ const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`;
+ return await prisma.game.upsert({
+ where: {
+ external_id: game.external_id
+ },
+ create: {
+ name: game.name,
+ slug: kebabCase(game.name),
+ description: game.description,
+ external_id: game.external_id,
+ url: externalUrl,
+ thumb_url: game.thumb_url,
+ image_url: game.image_url,
+ min_age: game.min_age || 0,
+ min_players: game.min_players || 0,
+ max_players: game.max_players || 0,
+ min_playtime: game.min_playtime || 0,
+ max_playtime: game.max_playtime || 0,
+ year_published: game.year_published || 0
+ },
+ update: {
+ name: game.name,
+ slug: kebabCase(game.name),
+ description: game.description,
+ external_id: game.external_id,
+ url: externalUrl,
+ thumb_url: game.thumb_url,
+ image_url: game.image_url,
+ min_age: game.min_age || 0,
+ min_players: game.min_players || 0,
+ max_players: game.max_players || 0,
+ min_playtime: game.min_playtime || 0,
+ max_playtime: game.max_playtime || 0,
+ year_published: game.year_published || 0
+ }
+ });
+}
+
+export async function createOrUpdateGame(game: GameType) {
+ console.log('Creating or updating game', JSON.stringify(game, null, 2));
+ const categoryIds = game.categories;
+ const mechanicIds = game.mechanics;
+ const publisherIds = game.publishers;
+ const designerIds = game.designers;
+ const artistIds = game.artists;
+ const expansionIds = game.expansions;
+ const externalUrl = `https://boardgamegeek.com/boardgame/${game.external_id}`;
+ console.log('categoryIds', categoryIds);
+ console.log('mechanicIds', mechanicIds);
+ return await prisma.game.upsert({
+ include: {
+ mechanics: true,
+ publishers: true,
+ designers: true,
+ artists: true,
+ expansions: true
+ },
+ where: {
+ external_id: game.external_id
+ },
+ create: {
+ name: game.name,
+ slug: kebabCase(game.name),
+ description: game.description,
+ external_id: game.external_id,
+ url: externalUrl,
+ thumb_url: game.thumb_url,
+ image_url: game.image_url,
+ min_age: game.min_age || 0,
+ min_players: game.min_players || 0,
+ max_players: game.max_players || 0,
+ min_playtime: game.min_playtime || 0,
+ max_playtime: game.max_playtime || 0,
+ year_published: game.year_published || 0,
+ last_sync_at: new Date(),
+ categories: {
+ connect: categoryIds
+ },
+ mechanics: {
+ connect: mechanicIds
+ },
+ publishers: {
+ connect: publisherIds
+ },
+ designers: {
+ connect: designerIds
+ },
+ artists: {
+ connect: artistIds
+ },
+ expansions: {
+ connect: expansionIds
+ }
+ },
+ update: {
+ name: game.name,
+ slug: kebabCase(game.name),
+ description: game.description,
+ external_id: game.external_id,
+ url: externalUrl,
+ thumb_url: game.thumb_url,
+ image_url: game.image_url,
+ min_age: game.min_age || 0,
+ min_players: game.min_players || 0,
+ max_players: game.max_players || 0,
+ min_playtime: game.min_playtime || 0,
+ max_playtime: game.max_playtime || 0,
+ year_published: game.year_published || 0,
+ last_sync_at: new Date(),
+ categories: {
+ connect: categoryIds
+ },
+ mechanics: {
+ connect: mechanicIds
+ },
+ publishers: {
+ connect: publisherIds
+ },
+ designers: {
+ connect: designerIds
+ },
+ artists: {
+ connect: artistIds
+ },
+ expansions: {
+ connect: expansionIds
+ }
+ }
+ });
+}
diff --git a/src/lib/utils/gameMapper.ts b/src/lib/utils/gameMapper.ts
index a0f81ba..a55d8f8 100644
--- a/src/lib/utils/gameMapper.ts
+++ b/src/lib/utils/gameMapper.ts
@@ -1,4 +1,5 @@
import type { GameType, SavedGameType } from '$lib/types';
+import kebabCase from 'just-kebab-case';
export function convertToSavedGame(game: GameType | SavedGameType): SavedGameType {
return {
@@ -41,56 +42,21 @@ export function mapSavedGameToGame(game: SavedGameType): GameType {
};
}
-// TODO: Type API response
-export function mapAPIGameToBoredGame(game: any): GameType {
- const {
- id,
- handle,
- name,
- url,
- thumb_url,
- image_url,
- year_published,
- categories,
- mechanics,
- primary_designer,
- designers,
- primary_publisher,
- publishers,
- artists,
- min_players,
- max_players,
- min_playtime,
- max_playtime,
- min_age,
- description,
- description_preview,
- players,
- playtime
- } = game;
+export function mapAPIGameToBoredGame(game: GameType): GameType {
+ // TODO: Fix types
return {
- id,
- handle,
- name,
- url,
- thumb_url,
- image_url,
- year_published,
- categories,
- mechanics,
- primary_designer,
- designers,
- primary_publisher,
- publishers,
- artists,
- min_players,
- max_players,
- min_playtime,
- max_playtime,
- min_age,
- description,
- description_preview,
- players,
- playtime
+ external_id: game.external_id,
+ name: game.name,
+ slug: kebabCase(game.name),
+ thumb_url: game.thumbnail,
+ image_url: game.image,
+ year_published: game.year_published,
+ min_players: game.min_players,
+ max_players: game.max_players,
+ min_playtime: game.min_playtime,
+ max_playtime: game.max_playtime,
+ min_age: game.min_age,
+ description: game.description,
+ playtime: game.playing_time
};
}
diff --git a/src/routes/(app)/game/[id]/+page.server.ts b/src/routes/(app)/game/[id]/+page.server.ts
index 2af373e..8e8b674 100644
--- a/src/routes/(app)/game/[id]/+page.server.ts
+++ b/src/routes/(app)/game/[id]/+page.server.ts
@@ -1,13 +1,24 @@
import { error } from '@sveltejs/kit';
import prisma from '$lib/prisma.js';
+import type { GameType } from '$lib/types.js';
+import { createArtist, createCategory, createDesigner, createExpansion, createMechanic, createOrUpdateGame, createPublisher } from '$lib/utils/dbUtils.js';
+import { mapAPIGameToBoredGame } from '$lib/utils/gameMapper.js';
+import type { Game } from '@prisma/client';
-export const load = async ({ params, setHeaders, locals }) => {
+export const load = async ({ params, setHeaders, locals, fetch }) => {
try {
const { user } = locals;
const { id } = params;
const game = await prisma.game.findUnique({
where: {
id
+ },
+ include: {
+ artists: true,
+ designers: true,
+ publishers: true,
+ mechanics: true,
+ categories: true
}
});
console.log('found game', game);
@@ -16,6 +27,11 @@ export const load = async ({ params, setHeaders, locals }) => {
throw error(404, 'not found');
}
+ const currentDate = new Date();
+ if (game.last_sync_at === null || currentDate.getDate() - game.last_sync_at.getDate() > 7 * 24 * 60 * 60 * 1000) {
+ await syncGameAndConnectedData(game, fetch);
+ }
+
let wishlist;
let collection;
if (user) {
@@ -60,3 +76,63 @@ export const load = async ({ params, setHeaders, locals }) => {
throw error(404, 'not found');
};
+
+async function syncGameAndConnectedData(game: Game, eventFetch: Function) {
+ console.log(
+ `Retrieving full external game details for external id: ${game.external_id} with name ${game.name}`
+ );
+ const externalGameResponse = await eventFetch(`/api/external/game/${game.external_id}`);
+ if (externalGameResponse.ok) {
+ const externalGame = await externalGameResponse.json();
+ console.log('externalGame', externalGame);
+ let categories = [];
+ let mechanics = [];
+ let artists = [];
+ let designers = [];
+ let publishers = [];
+ let expansions = [];
+ for (const externalCategory of externalGame.categories) {
+ const category = await createCategory(externalCategory);
+ categories.push({
+ id: category.id
+ });
+ }
+ for (const externalMechanic of externalGame.mechanics) {
+ const mechanic = await createMechanic(externalMechanic);
+ mechanics.push({ id: mechanic.id });
+ }
+ for (const externalArtist of externalGame.artists) {
+ const artist = await createArtist(externalArtist);
+ artists.push({ id: artist.id });
+ }
+ for (const externalDesigner of externalGame.designers) {
+ const designer = await createDesigner(externalDesigner);
+ designers.push({ id: designer.id });
+ }
+ for (const externalPublisher of externalGame.publishers) {
+ const publisher = await createPublisher(externalPublisher);
+ publishers.push({ id: publisher.id });
+ }
+
+ for (const externalExpansion of externalGame.expansions) {
+ let expansion;
+ console.log('Inbound?', externalExpansion.inbound);
+ if (externalExpansion?.inbound === true) {
+ expansion = await createExpansion(game, externalExpansion, false, eventFetch);
+ } else {
+ expansion = await createExpansion(game, externalExpansion, true, eventFetch);
+ }
+ expansions.push({ id: expansion.id });
+ }
+
+ let boredGame = mapAPIGameToBoredGame(externalGame);
+
+ boredGame.categories = categories;
+ boredGame.mechanics = mechanics;
+ boredGame.designers = designers;
+ boredGame.artists = artists;
+ boredGame.publishers = publishers;
+ boredGame.expansions = expansions;
+ return createOrUpdateGame(boredGame);
+ }
+}
\ No newline at end of file
diff --git a/src/routes/(app)/game/[id]/+page.svelte b/src/routes/(app)/game/[id]/+page.svelte
index 07d1ea6..c81e6a4 100644
--- a/src/routes/(app)/game/[id]/+page.svelte
+++ b/src/routes/(app)/game/[id]/+page.svelte
@@ -1,16 +1,13 @@
Year: {game?.year_published}
- {/if} {#if game?.min_players && game?.max_players}Players: {game.min_players} - {game.max_players}
{/if} @@ -57,10 +51,12 @@ {#if game?.min_age}Minimum Age: {game.min_age}
{/if} -Playtime: {game.min_playtime} - {game.max_playtime} minutes
+ {/if} +