From 72defd7e7b990992a3c815f0c0e84e57c01a40c8 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:32:09 +0000 Subject: [PATCH 1/2] Initial plan From f98e2a5dfa9ed90dc21877e2b1317a50305ef654 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Thu, 24 Sep 2026 10:34:36 +0000 Subject: [PATCH 2/2] Add related games helper and tests Co-authored-by: Samrudh9 <84171396+Samrudh9@users.noreply.github.com> --- src/lib/games.test.ts | 84 +++++++++++++++++++++++++++++++++++++++ src/lib/games.ts | 27 ++++++++++++- src/pages/game/[id].astro | 33 ++++++++++++++- 3 files changed, 141 insertions(+), 3 deletions(-) diff --git a/src/lib/games.test.ts b/src/lib/games.test.ts index efea6b6..375859c 100644 --- a/src/lib/games.test.ts +++ b/src/lib/games.test.ts @@ -1,4 +1,5 @@ import { describe, it, expect, beforeEach } from 'vitest'; +import { eq } from 'drizzle-orm'; import { createTestDatabase } from '../../db/test-helpers'; import { categories, publishers, games } from '../../db/schema'; import type { Database } from './db'; @@ -6,6 +7,7 @@ import { getAllGames, getAllGameIds, getGameById, + getRelatedGames, } from './games'; async function seedGames(db: Database, count: number): Promise { @@ -63,4 +65,86 @@ describe('games data-access helpers', () => { await seedGames(db, 2); expect(await getGameById(db, 99999)).toBeNull(); }); + + it('returns related games from the same category excluding the source game', async () => { + const [strategyCategory] = await db + .insert(categories) + .values({ name: 'Strategy', description: 'strategy category' }) + .returning({ id: categories.id }); + const [adventureCategory] = await db + .insert(categories) + .values({ name: 'Adventure', description: 'adventure category' }) + .returning({ id: categories.id }); + const [publisher] = await db + .insert(publishers) + .values({ name: 'Pub One', description: 'pub' }) + .returning({ id: publishers.id }); + + await db.insert(games).values([ + { + title: 'Core Strategy', + description: 'source', + starRating: 4.5, + categoryId: strategyCategory.id, + publisherId: publisher.id, + }, + { + title: 'Alpha Tactics', + description: 'related 1', + starRating: 4.2, + categoryId: strategyCategory.id, + publisherId: publisher.id, + }, + { + title: 'Zenith Plans', + description: 'related 2', + starRating: 4.4, + categoryId: strategyCategory.id, + publisherId: publisher.id, + }, + { + title: 'Forest Quest', + description: 'different category', + starRating: 4.0, + categoryId: adventureCategory.id, + publisherId: publisher.id, + }, + ]); + + const source = await db + .select({ id: games.id }) + .from(games) + .where(eq(games.title, 'Core Strategy')) + .get(); + + expect(source).not.toBeUndefined(); + + const related = await getRelatedGames(db, source!.id); + expect(related.map((game) => game.title)).toEqual(['Alpha Tactics', 'Zenith Plans']); + }); + + it('returns an empty list when no related games exist', async () => { + const [category] = await db + .insert(categories) + .values({ name: 'Puzzle', description: 'puzzle category' }) + .returning({ id: categories.id }); + const [publisher] = await db + .insert(publishers) + .values({ name: 'Solo Pub', description: 'pub' }) + .returning({ id: publishers.id }); + + const [insertedGame] = await db + .insert(games) + .values({ + title: 'Only Puzzle Game', + description: 'single game in category', + starRating: 3.8, + categoryId: category.id, + publisherId: publisher.id, + }) + .returning({ id: games.id }); + + const related = await getRelatedGames(db, insertedGame.id); + expect(related).toEqual([]); + }); }); diff --git a/src/lib/games.ts b/src/lib/games.ts index 4ed218e..426a32f 100644 --- a/src/lib/games.ts +++ b/src/lib/games.ts @@ -1,4 +1,8 @@ -import { eq, asc } from 'drizzle-orm'; +/** + * Data-access helpers for retrieving games and related metadata from SQLite. + */ + +import { and, asc, eq, ne } from 'drizzle-orm'; import type { Database } from './db'; import { games, categories, publishers } from '../../db/schema'; import type { Game } from '../types/game'; @@ -67,3 +71,24 @@ export async function getGameById(db: Database, id: number): Promise { + const gameRow = await db + .select({ categoryId: games.categoryId }) + .from(games) + .where(eq(games.id, id)) + .get(); + + if (!gameRow || gameRow.categoryId === null) { + return []; + } + + const rows = await baseGamesQuery(db) + .where(and(eq(games.categoryId, gameRow.categoryId), ne(games.id, id))) + .orderBy(asc(games.title)); + + return rows.map(mapGame); +} diff --git a/src/pages/game/[id].astro b/src/pages/game/[id].astro index eb9ebdf..cbd036b 100644 --- a/src/pages/game/[id].astro +++ b/src/pages/game/[id].astro @@ -1,14 +1,19 @@ --- +/** + * Static game details page with category-based related-game recommendations. + */ + import type { GetStaticPaths } from 'astro'; import Layout from '../../layouts/Layout.astro'; import StarRating from '../../components/StarRating.astro'; import Tag from '../../components/Tag.astro'; import Card from '../../components/Card.astro'; import Button from '../../components/Button.astro'; +import EmptyState from '../../components/EmptyState.astro'; import ArrowLeft from '../../assets/icons/arrow-left.svg'; import Heart from '../../assets/icons/heart.svg'; import { getDatabase } from '../../lib/db'; -import { getAllGameIds, getGameById } from '../../lib/games'; +import { getAllGameIds, getGameById, getRelatedGames } from '../../lib/games'; export const prerender = true; @@ -18,11 +23,14 @@ export const getStaticPaths = (async () => { }) satisfies GetStaticPaths; const { id } = Astro.params; -const game = await getGameById(getDatabase(), Number(id)); +const database = getDatabase(); +const game = await getGameById(database, Number(id)); if (!game) { return Astro.redirect('/404'); } + +const relatedGames = await getRelatedGames(database, game.id); --- @@ -55,6 +63,27 @@ if (!game) { +
+

Related games

+ {relatedGames.length === 0 ? ( + + ) : ( + + )} +
+