Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 99 additions & 30 deletions backend/src/resolvers.ts
Original file line number Diff line number Diff line change
@@ -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;
Expand All @@ -29,73 +38,133 @@ 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
WHERE id = $1
`;
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])
return res.rows[0] ?? null;
};

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'),
Expand Down
16 changes: 14 additions & 2 deletions backend/src/schema.ts
Original file line number Diff line number Diff line change
@@ -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.
"""
Expand Down Expand Up @@ -27,7 +38,6 @@ export const typeDefs = `#graphql
genres: [Facet!]!
platforms: [Facet!]!
parentPlatforms: [Facet!]!
stores: [Facet!]!
developers: [Facet!]!
publishers: [Facet!]!
Expand Down Expand Up @@ -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!]!
}
`;
Loading