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
9 changes: 8 additions & 1 deletion backend/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,9 @@
DATABASE_URL=postgresql://gameapp:<FYLL-INN-PASSORD>@localhost:5432/gamesdb
# tunnel script env vars
VM_USER=<ntnu-brukernavn>
VM_HOST=it2810-02.idi.ntnu.no
TUNNEL_PORT=5433

# backend server env vars
DATABASE_URL=postgresql://webapp:<PASSORD>@localhost:5433/gamesdb
PORT=3001
JWT_TOKEN=<server-rand-token>
2 changes: 1 addition & 1 deletion backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
"main": "index.js",
"type": "module",
"scripts": {
"dev": "tsx watch src/index.ts",
"dev": "tsx watch --env-file=.env src/index.ts",
"compile": "tsc",
"build": "tsc",
"start": "pnpm run build && node dist/index.js",
Expand Down
3 changes: 3 additions & 0 deletions backend/src/auth.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@

// server jwt secret
// const secret = process.env.JWT_SECRET;
7 changes: 7 additions & 0 deletions backend/src/db.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
import pg from 'pg';

const url = process.env.DATABASE_URL;
if (!url) throw new Error("DATABASE_URL not set in .env ...")

//pg pool
export const pool = new pg.Pool({ connectionString: process.env.DATABASE_URL})
44 changes: 9 additions & 35 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,47 +1,21 @@
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { Query } from "pg";
import { start } from "repl";
import { pool } from './db.js';
import { typeDefs } from './schema.js';
import { resolvers } from './resolvers.js';

const typeDefs = `#graphql
# comment
type Book {
title: String
author: String
}
type Query {
books: [Book]
}
`;

const books = [
{
title: 'The awakening',
author: 'Kate Chopin',
},
{
title: 'City of glass',
author: 'Paul Auster',
},
];

const resolvers = {
Query: {
books: () => books,
},
};
// apollo-server port number [default port 3001]
const port = Number(process.env.PORT ?? 3001)

// tester db tilkobling med en query
await pool.query('SELECT 1');

// apollo server instance
const server = new ApolloServer({
typeDefs,
resolvers,
});
const server = new ApolloServer({ typeDefs, resolvers });

// pass apolloserver to standaloneserver
const { url } = await startStandaloneServer(server, {
listen: { port: 4000 },
listen: { port },
});

console.log(`Server ready at: ${url}`);
89 changes: 89 additions & 0 deletions backend/src/resolvers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
import { pool } from './db.js';

const VALID_ID_REGEX = /^\d{1,18}$/;
const MAX_PAGE_SIZE = 50;

// definerer hvilke kolonner som skal hentes for en generell
// forespørsel om et spill, inkludert oversetting fra databasen
// sin navnekonvensjon til GraphQL, og konvertering av typer
const GAME_COLS = `
id,
name,
description,
released::text AS "released",
website,
added,
metacritic,
esrb_rating AS "esrbRating",
dominant_color AS "dominantColor",
background_image AS "backgroundImage",
background_image_additional AS "backgroundImageAdditional",
COALESCE(screenshots, '{}') AS "screenshots"
`;

// SQL queries
const SQL_COUNT = `
SELECT count(*)::int AS count FROM games
`;

const SQL_GAME = `
SELECT ${GAME_COLS} FROM games
WHERE id = $1
`;

const SQL_GAMES = `
SELECT ${GAME_COLS} FROM games
ORDER BY added DESC, id DESC
LIMIT $1
`;

// resolver for koblings-tabellene i databasen
const 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;
};
};

// resolvers for å hente kjøre spørringer mot databasen
export const resolvers = {
Query: {
// ett spill basert på id
game: async (_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;
},

// liste med spill formattert som en GameConnection
games: async (_parent: unknown, args: { first: number }) => {
const first = Math.min(Math.max(args.first, 1), MAX_PAGE_SIZE);
const res = await pool.query(SQL_GAMES, [first]);
return {
edges: res.rows.map((row) => ({ node: row, cursor: '' })),
pageInfo: { hasNextPage: false, endCursor: null},
};
}
},

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'),
tags: relation('tags', 'game_tags', 'tag_id'),
},

GameConnection: {
totalCount: async() => { return (await pool.query(SQL_COUNT)).rows[0].count; }
}
};
71 changes: 71 additions & 0 deletions backend/src/schema.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@

export const typeDefs = `#graphql
"""
Generell type for attributter som sjanger, platform, etc.
"""
type Facet {
id: ID!
name: String!
}
"""
Game holder all informasjon om ett enkelt spill.
"""
type Game {
id: ID!,
name: String!,
description: String,
released: String,
website: String,
added: Int!,
metacritic: Int,
esrbRating: String,
dominantColor: String,
backgroundImage: String,
backgroundImageAdditional: String,
screenshots: [String!]!
genres: [Facet!]!
platforms: [Facet!]!
parentPlatforms: [Facet!]!
stores: [Facet!]!
developers: [Facet!]!
publishers: [Facet!]!
tags: [Facet!]!
}
"""
Resultatet av en 'games' query, dette returnerer en 'page'
med mange 'edges' (spill) og kontroll-informasjon for pagination.
"""
type GameConnection {
edges: [GameEdge!]!
pageInfo: PageInfo!
totalCount: Int!
}
"""
Én 'game-edge' fra GameConnection. Inneholder informasjon om spillet (Game)
og posisjonen til spillet i lista (cursor).
"""
type GameEdge {
node: Game!
cursor: String!
}
"""
Info om nåværende page, kontrollinformasjon for neste forespørsel.
"""
type PageInfo {
hasNextPage: Boolean!
endCursor: String
}
"""
Alle gyldige queries mot backend er definert her.
"""
type Query {
game(id: ID!): Game
games(first: Int=20): GameConnection!
}
`;
Loading