Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
86e3058
feature: db scripts, apollo install
Oct 2, 2026
2ff6a31
Merge pull request #18 from IT2810-H26/feature/apollo-server
albertpl Oct 2, 2026
4c60d7f
la til enkelt eksempel med apollo
Oct 2, 2026
4bd4544
Create ssh-tunnel.md
albertpl Oct 8, 2026
d532d44
Update database-usage.md
albertpl Oct 8, 2026
40b14e4
Create grants.sql
albertpl Oct 8, 2026
bded41f
feat(frontend): legg til GameSummary-type for spillkort
laspark Oct 8, 2026
ac70bf5
feat(frontend): legg til GameCard-komponent
laspark Oct 8, 2026
885d705
feat(frontend): stil for GameCard
laspark Oct 8, 2026
b75b8f7
Merge pull request #19 from IT2810-H26/feature/spillkort
johabort Oct 9, 2026
759c451
feat(frontend): legg til view-prop i GameCard for liste og rutenett
laspark Oct 9, 2026
68b16e3
style(frontend): mindre bilde i liste og ny rutenett-visning for Game…
laspark Oct 9, 2026
1303f64
feat(frontend): legg til GameList som viser spill i liste eller rutenett
laspark Oct 9, 2026
9a16951
style(frontend): layout for GameList (4 per rad i rutenett)
laspark Oct 9, 2026
015cb55
chore(frontend): midlertidige mockdata for spill til GraphQL er klart
laspark Oct 9, 2026
967d0f0
feat(frontend): vis spill-lista med bytte mellom liste og rutenett
laspark Oct 9, 2026
ce63258
Merge pull request #22 from IT2810-H26/feature/spillkort
johabort Oct 9, 2026
015fbb8
feat(frontend): legg til useDebounce-hook
Oct 9, 2026
1a01103
feat(frontend): legg til SearchBar-komponent
Oct 9, 2026
567f324
style(frontend): firkantet søkefelt med skjult label og fokusramme
Oct 9, 2026
a72e089
Update database-setup.md
albertpl Oct 9, 2026
05a0f6f
feat(frontend): koble søk til spill-lista (midlertidig klientfiltreri…
Oct 9, 2026
ffd0f58
Merge pull request #23 from IT2810-H26/feature/apollo-server
albertpl Oct 9, 2026
a5f1664
Merge pull request #24 from IT2810-H26/feature/sok
clemenbe Oct 9, 2026
48a11b9
flere felter i tabellene
Oct 9, 2026
e40317b
lagt inn logo top left, og fjernet SPILL
Oct 9, 2026
ff2f1e5
Merge pull request #41 from IT2810-H26/feat/logo-top-right
clemenbe Oct 9, 2026
9414016
Merge branch 'dev' into server/fix-db-import
albertpl Oct 9, 2026
e300f65
Merge pull request #42 from IT2810-H26/server/fix-db-import
albertpl Oct 9, 2026
a457414
#27 fungerende graphql server
Oct 12, 2026
e512f22
readd .env.*
Oct 12, 2026
e1f726c
Merge pull request #45 from IT2810-H26/feature/graphql-server
albertpl Oct 12, 2026
ef2d526
search
Oct 12, 2026
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>
7 changes: 4 additions & 3 deletions backend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,17 @@
"version": "1.0.0",
"description": "",
"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": "node dist/index.js",
"start": "pnpm run build && node dist/index.js",
"lint": "eslint ."
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"@apollo/server": "^5.5.1",
"dotenv": "^18.0.5",
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})
23 changes: 21 additions & 2 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,21 @@
// placeholder, erstattes av apollo server senere
console.log('backend');
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { pool } from './db.js';
import { typeDefs } from './schema.js';
import { resolvers } from './resolvers.js';

// 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 });

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

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

// type definitions
type GamesArgs = {
search?: string | null;
first: number;
};

// const vars and func
const VALID_ID_REGEX = /^\d{1,18}$/;
const MAX_PAGE_SIZE = 50;
const escapeChars = (s: string) => s.replace(/[\\%_]/g, '\\$&')

// 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 GAMES_PATTERN = `
WHERE ($1::text IS NULL
OR search_names ILIKE $1)
`;
const SQL_COUNT = `
SELECT count(*)::int AS count FROM games
${GAMES_PATTERN}
`;
const SQL_GAME = `
SELECT ${GAME_COLS} FROM games
WHERE id = $1
`;
const SQL_GAMES = `
SELECT ${GAME_COLS} FROM games
${GAMES_PATTERN}
ORDER BY added DESC, id DESC
LIMIT $2
`;

// game resolvers
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 first = Math.min(Math.max(args.first, 1), MAX_PAGE_SIZE);

const res = await pool.query(SQL_GAMES, [pattern, first]);
return {
edges: res.rows.map((row) => ({ node: row, cursor: '' })),
pageInfo: { hasNextPage: false, endCursor: null},
pattern,
};
}

async function totalCount(parent: { pattern: string | null }) {
return (await pool.query(SQL_COUNT, [parent.pattern])).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;
};
};

// resolvers for å hente kjøre spørringer mot databasen
export const resolvers = {
Query: { game, games },

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 }
};
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(search: String, first: Int=20): GameConnection!
}
`;
4 changes: 4 additions & 0 deletions db/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
VM_USER=brukernavn
VM_HOST=it2810-02.idi.ntnu.no
TUNNEL_PORT=5433
DATABASE_URL=postgresql://gameapp:PASSORD@localhost:5433/gamesdb
23 changes: 23 additions & 0 deletions db/database-connect.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@

#### Database-connect

Scripts for å koble seg til database ligger i `<root>/scripts`.

###### db-tunnel.sh

Spesifikt inneholder denne `db-tunnel.sh` som oppretter en ssh tunnel til vm over porten `5432`. Dette gjør at all trafikk som normalt går til `localhost:5432` rutes til postgres på serveren, slik at vi slipper å laste ned hele databasen på hver av pc-ene våre.

`db-tunnel.sh` scriptet er lagt inn som et script i `<root>/package.json`, og kan kjøres med `pnpm db:tunnel`.

###### test-select.sh

Det andre scriptet er `test-select.sh`, dette er et enkelt script som kjører en spørring mot en database på `localhost:5432`. Hvis tunnellen fungerer skal dette returnere dette:

```
count
--------
120067
(1 row)
```

For å kunne kjøre dette scriptet trenger man postgres-client. Dette kan lastes ned med `sudo apt update && sudo apt install postgresql-client` på debian/ubuntu, og med `brew install libpq` på mac.
5 changes: 4 additions & 1 deletion db/database-setup.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,9 @@ sudo -u postgres psql

```sql
CREATE USER gameapp WITH PASSWORD '<passord>';
CREATE ROLE webapp WITH LOGIN PASSWORD '<passord>';
CREATE DATABASE gamesdb OWNER gameapp;

\c gamesdb
CREATE EXTENSION IF NOT EXISTS pg_trgm;
\q
Expand Down Expand Up @@ -52,7 +54,8 @@ cd backend/db
python3 -m venv .venv && source .venv/bin/activate
pip install -r requirements.txt
export DATABASE_URL='postgresql://gameapp:<passord>@localhost:5432/gamesdb'
python load.py
python load.py --force
psql "$DATABASE_URL" -f grants.sql
```

`load.py` leser Parquet-filen med DuckDB, deler opp flerverdi-felter til oppslags- og koblingstabeller, laster alt med `COPY`, oppretter indekser etter lasting og kjører `ANALYZE`. Filstien kan overstyres med `PARQUET_PATH`. Finnes tabellene allerede, avbryter scriptet. `python load.py --force` sletter og laster alt på nytt, inkludert reviews.
Expand Down
53 changes: 52 additions & 1 deletion db/database-usage.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,32 @@
## Roller for databasen i postgres

#### Database-usage
|Rolle|Bruk|Rettigheter|
|-|-|-|
|postgres|Administrasjon|Superuser|
|gameapp|Import og schemaendringer|Eier av gamesdb|
|webapp|Backend|SELECT på alt, INSERT på reviews|

## Koble til postgres
#### Fra vm-en:
```bash
psql -h localhost -U webapp -d gamesdb
psql -h localhost -U gameapp -d gamesdb
sudo -u postgres psql -d gamesdb
```

#### Fra egen maskin (`pnpm db:tunnel`):
```bash
psql -h localhost -p 5433 -U webapp -d gamesdb
```

#### Backend bruker `DATABASE_URL` i `backend/.env`:
```
postgresql://webapp:PASSORD@localhost:5433/gamesdb
```

Port `5433` lokalt via tunnel, `5432` på VM-en.

## Bruk av database

Under er noen generelle kommandoer som kan brukes i postgres for navigere databasen:

Expand Down Expand Up @@ -27,3 +54,27 @@ SELECT * FROM games LIMIT 5;
SELECT metacritic FROM games WHERE id = 4291;

```

## Import og rettigheter

Filer i `backend/db/`: `schema.sql`, `indexes.sql`, `grants.sql`, `load.py`, `requirements.txt`.

_Full reimport på VM-en (sletter også reviews)_:
```
cd backend/db && source .venv/bin/activate
export DATABASE_URL='postgresql://gameapp:PASSORD@localhost:5432/gamesdb'
python load.py --force
psql "$DATABASE_URL" -f grants.sql
```

>[!NOTE]
>`grants.sql` må kjøres etter hver reimport, siden tabellene gjenopprettes og mister rettighetene.
_Sjekk status på tjenesten_:
```bash
sudo systemctl status postgresql
sudo systemctl restart postgresql
sudo ss -tlnp | grep 5432
```

Siste skal kun vise `127.0.0.1:5432` og `[::1]:5432`.
9 changes: 9 additions & 0 deletions db/grants.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
-- Rettigheter for backend rollen webapp. Kjør etter hver import!
-- Rollen skal opprettes èn gang i postgres før scriptet kjøres:
-- CREATE ROLE webapp WITH LOGIN PASSWORD '<password>';
GRANT CONNECT ON DATABASE gamesdb TO webapp;
GRANT USAGE ON SCHEMA public TO webapp;

GRANT SELECT ON ALL TABLES IN SCHEMA public TO webapp;
GRANT INSERT ON reviews TO webapp;
GRANT USAGE ON SEQUENCE reviews_id_seq TO webapp;
Loading
Loading