From 69308656131e41c881ee8a0ad304a4ca095eb3c0 Mon Sep 17 00:00:00 2001 From: albdert Date: Mon, 12 Oct 2026 15:04:15 +0200 Subject: [PATCH] filtering! --- backend/src/resolvers.ts | 129 ++++++++++++++++++++++++++++++--------- backend/src/schema.ts | 16 ++++- 2 files changed, 113 insertions(+), 32 deletions(-) diff --git a/backend/src/resolvers.ts b/backend/src/resolvers.ts index 3df9190..9a9e10a 100644 --- a/backend/src/resolvers.ts +++ b/backend/src/resolvers.ts @@ -1,11 +1,20 @@ +import { assertName } from 'graphql'; import { pool } from './db.js'; // type definitions type GamesArgs = { search?: string | null; + filter?: GameFilter | null; first: number; }; +type GameFilter = { + genreIds?: string[] | null; + platformIds?: string[] | null; + releasedFrom?: number | null; + releasedTo?: number | null; +} + // const vars and func const VALID_ID_REGEX = /^\d{1,18}$/; const MAX_PAGE_SIZE = 50; @@ -29,14 +38,52 @@ const GAME_COLS = ` COALESCE(screenshots, '{}') AS "screenshots" `; -// SQL queries -const GAMES_PATTERN = ` - WHERE ($1::text IS NULL - OR search_names ILIKE $1) +// SQL filtrering +const WHERE_SEARCH = ` + ($1::text IS NULL OR + search_names ILIKE $1) +`; +const WHERE_GENRES = ` + ($2::int[] IS NULL OR ( + SELECT count(*) FROM game_genres gg + WHERE gg.game_id = games.id + AND gg.genre_id = ANY($2)) = cardinality($2)) +`; +const WHERE_PLATFORM = ` + ($3::int[] IS NULL OR EXISTS ( + SELECT 1 FROM game_parent_platforms gp + WHERE gp.game_id = games.id + AND gp.parent_platform_id = ANY($3))) +`; +const WHERE_RELEASED_FROM = ` + ($4::int IS NULL OR + released >= make_date($4, 1, 1)) +`; +const WHERE_RELEASED_TO = ` + ($5::int IS NULL OR + released < make_date($5 + 1, 1, 1)) `; +const GAMES_WHERE = ` + WHERE ${WHERE_SEARCH} + AND ${WHERE_GENRES} + AND ${WHERE_PLATFORM} + AND ${WHERE_RELEASED_FROM} + AND ${WHERE_RELEASED_TO} +`; + + +// SQL queries const SQL_COUNT = ` SELECT count(*)::int AS count FROM games - ${GAMES_PATTERN} + ${GAMES_WHERE} +`; +const SQL_GENRES = ` + SELECT id, name FROM genres + ORDER BY name +`; +const SQL_PLATFORMS = ` + SELECT id, name FROM parent_platforms + ORDER BY name `; const SQL_GAME = ` SELECT ${GAME_COLS} FROM games @@ -44,12 +91,43 @@ const SQL_GAME = ` `; const SQL_GAMES = ` SELECT ${GAME_COLS} FROM games - ${GAMES_PATTERN} + ${GAMES_WHERE} ORDER BY added DESC, id DESC - LIMIT $2 + LIMIT $6 `; -// game resolvers +// hjelpefunksjoner +function toIds(ids?: string[] | null) { + const nums = (ids??[]).map(Number).filter(Number.isInteger); + return nums.length? nums : null; +} +// returnerer array med filter-verdiene +function filterValues(args: GamesArgs) { + const term = args.search?.trim(); + return [ + term? `%${escapeChars(term)}%` : null, + toIds(args.filter?.genreIds), + toIds(args.filter?.platformIds), + args.filter?.releasedFrom??null, + args.filter?.releasedTo??null, + ]; +} +// resolver for koblings-tabellene i databasen +function relation(table: string, junc: string, fk: string) { + const sql = ` + SELECT t.id, t.name + FROM ${junc} j + JOIN ${table} t ON t.id = j.${fk} + WHERE j.game_id = $1 + ORDER BY t.name + `; + return async (game: {id: string }) => { + const res = await pool.query(sql, [game.id]); + return res.rows; + }; +}; + +// resolver-funksjoner async function game(_parent: unknown, args: { id: string }) { if (!VALID_ID_REGEX.test(args.id)) { return null; } const res = await pool.query(SQL_GAME, [args.id]) @@ -57,45 +135,36 @@ async function game(_parent: unknown, args: { id: string }) { }; async function games(_parent: unknown, args: GamesArgs ) { - const term = args.search?.trim(); - const pattern = term?`%${escapeChars(term)}%`:null; + const values = filterValues(args); const first = Math.min(Math.max(args.first, 1), MAX_PAGE_SIZE); - const res = await pool.query(SQL_GAMES, [pattern, first]); + const res = await pool.query(SQL_GAMES, [...values, first]); return { edges: res.rows.map((row) => ({ node: row, cursor: '' })), pageInfo: { hasNextPage: false, endCursor: null}, - pattern, + values, }; } -async function totalCount(parent: { pattern: string | null }) { - return (await pool.query(SQL_COUNT, [parent.pattern])).rows[0].count; +async function totalCount(parent: { values: unknown[] }) { + return (await pool.query(SQL_COUNT, parent.values)).rows[0].count; } -// resolver for koblings-tabellene i databasen -function relation(table: string, junc: string, fk: string) { - const sql = ` - SELECT t.id, t.name - FROM ${junc} j - JOIN ${table} t ON t.id = j.${fk} - WHERE j.game_id = $1 - ORDER BY t.name - `; - return async (game: {id: string }) => { - const res = await pool.query(sql, [game.id]); - return res.rows; - }; -}; +async function genres() { + return (await pool.query(SQL_GENRES)).rows; +} + +async function platforms() { + return (await pool.query(SQL_PLATFORMS)).rows; +} // resolvers for å hente kjøre spørringer mot databasen export const resolvers = { - Query: { game, games }, + Query: { game, games, genres, platforms }, Game: { genres: relation('genres', 'game_genres', 'genre_id'), platforms: relation('platforms', 'game_platforms', 'platform_id'), - parentPlatforms: relation('parent_platforms', 'game_parent_platforms', 'parent_platform_id'), stores: relation('stores', 'game_stores', 'store_id'), developers: relation('developers', 'game_developers', 'developer_id'), publishers: relation('publishers', 'game_publishers', 'publisher_id'), diff --git a/backend/src/schema.ts b/backend/src/schema.ts index e8bb086..fa8b1eb 100644 --- a/backend/src/schema.ts +++ b/backend/src/schema.ts @@ -1,5 +1,16 @@ export const typeDefs = `#graphql + """ + Definerer input argumentene som er gyldige mot serveren. + Brukes for å filtrere visningen av spill. + """ + input GameFilter { + genreIds: [ID!] + platformIds: [ID!] + releasedFrom: Int + releasedTo: Int + } + """ Generell type for attributter som sjanger, platform, etc. """ @@ -27,7 +38,6 @@ export const typeDefs = `#graphql genres: [Facet!]! platforms: [Facet!]! - parentPlatforms: [Facet!]! stores: [Facet!]! developers: [Facet!]! publishers: [Facet!]! @@ -66,6 +76,8 @@ export const typeDefs = `#graphql """ type Query { game(id: ID!): Game - games(search: String, first: Int=20): GameConnection! + games(search: String, filter: GameFilter, first: Int=20): GameConnection! + genres: [Facet!]! + platforms: [Facet!]! } `; \ No newline at end of file