Skip to content
Closed

Dev #26

Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
24 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
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
5 changes: 3 additions & 2 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",
"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
49 changes: 47 additions & 2 deletions backend/src/index.ts
Original file line number Diff line number Diff line change
@@ -1,2 +1,47 @@
// placeholder, erstattes av apollo server senere
console.log('backend');
import { ApolloServer } from "@apollo/server";
import { startStandaloneServer } from "@apollo/server/standalone";
import { Query } from "pg";
import { start } from "repl";

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 instance
const server = new ApolloServer({
typeDefs,
resolvers,
});

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

console.log(`Server ready at: ${url}`);
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`.
6 changes: 6 additions & 0 deletions db/grants.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
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;
53 changes: 53 additions & 0 deletions db/ssh-tunnel.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
## Felles
#### Konfigurere `.env`
1. Koble til NTNU VPN hvis du er utenfor campus.
2. Kopier `backend/.env.example` til `backend/.env`
3. Fyll inn verdien til `VM_USER`, og `PASSORD` i `DATABASE_URL`.

`backend/.env` skal da se ut slik:
```
VM_USER=<ntnu-brukernavn>
VM_HOST=it2810-02.idi.ntnu.no
DATABASE_URL=postgresql://webapp:<PASSORD>@localhost:5433/gamesdb
PORT=3001
TUNNEL_PORT=5433
```

#### Start tunnelen
Gå til rot i repo (dvs. `T02-Project-2` mappen) og kjør kommandoen:
```bash
pnpm db:tunnel
```

La terminalen stå åpen, og start backend i en annen terminal med `pnpm --filter backend dev`.

## Windows

OpenSSH følger med Windows 10 og 11. Bash-scriptet fungerer ikke via pnpm db:tunnel, fordi pnpm kjører scripts gjennom cmd.

#### Start tunnelen i PowerShell:
```powershell
ssh -N -L 5433:localhost:5432 brukernavn@it2810-02.idi.ntnu.no
```
Alternativt kan scriptet kjøres direkte i Git Bash eller WSL med ./scripts/db-tunnel.sh. Portsjekken i scriptet bruker lsof, som ikke finnes i Git Bash, men den hoppes da bare over.

## Verifiser

Med tunnelen åpen, fra rot i repoet:

```bash
pnpm --filter backend exec tsx --env-file=.env -e "
import pg from 'pg';
const c = new pg.Client(process.env.DATABASE_URL);
await c.connect();
console.log((await c.query('SELECT count(*Kopier backend/.env.example til backend/.env og fyll inn VM_USER, VM_HOST, TUNNEL_PORT og DATABASE_URL (med webapp-rollen og port 5433).) FROM games')).rows);
await c.end();
"
```

Skal skrive ut `[ { count: '120067' } ]`.

#### Mulige feil
1. Connection refused på 5433 betyr at tunnelen ikke kjører.
2. Connection timed out mot VM-en betyr som regel at VPN mangler.
3. Password authentication failed betyr feil passord eller rolle i DATABASE_URL.
140 changes: 49 additions & 91 deletions frontend/src/App.tsx
Original file line number Diff line number Diff line change
@@ -1,101 +1,59 @@
import { useState } from 'react';
import heroImg from './assets/hero.png';
import reactLogo from './assets/react.svg';
import viteLogo from './assets/vite.svg';
import './App.css';
import { GameList } from './components/GameList/GameList';
import { mockGames } from './mocks/games';
import { SearchBar } from './components/SearchBar/SearchBar';
import { useDebounce } from './hooks/useDebounce';

/**
* Midlertidig forside: viser spillene med mockdata til GraphQL er klart.
* Favoritter og valgt visning ligger bare i minnet her og skal senere inn i state management.
*/
function App() {
const [count, setCount] = useState(0);
const [favorites, setFavorites] = useState<Set<string>>(new Set());
const [view, setView] = useState<'list' | 'grid'>('list');

return (
<>
<section id="center">
<div className="hero">
<img src={heroImg} className="base" width="170" height="179" alt="" />
<img src={reactLogo} className="framework" alt="React logo" />
<img src={viteLogo} className="vite" alt="Vite logo" />
</div>
<div>
<h1>Get started</h1>
<p>
Edit <code>src/App.tsx</code> and save to test <code>HMR</code>
</p>
</div>
<button type="button" className="counter" onClick={() => setCount((count) => count + 1)}>
Count is {count}
</button>
</section>
// Det brukeren skriver nå, og det vi faktisk søker på etter 300 ms pause
const [input, setInput] = useState('');
const query = useDebounce(input.trim(), 300);

// MIDLERTIDIG: filtrerer mockdata på klienten for å kunne teste søket.
// Når GraphQL er klart, sendes `query` til backend og filtreres i databasen.
const games = mockGames.filter((g) =>
g.name.toLowerCase().includes(query.toLowerCase()),
);

<div className="ticks"></div>
const toggleFavorite = (id: string) => {
setFavorites((prev) => {
const next = new Set(prev);
if (next.has(id)) next.delete(id);
else next.add(id);
return next;
});
};

<section id="next-steps">
<div id="docs">
<svg className="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#documentation-icon"></use>
</svg>
<h2>Documentation</h2>
<p>Your questions, answered</p>
<ul>
<li>
<a href="https://vite.dev/" target="_blank">
<img className="logo" src={viteLogo} alt="" />
Explore Vite
</a>
</li>
<li>
<a href="https://react.dev/" target="_blank">
<img className="button-icon" src={reactLogo} alt="" />
Learn more
</a>
</li>
</ul>
</div>
<div id="social">
<svg className="icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#social-icon"></use>
</svg>
<h2>Connect with us</h2>
<p>Join the Vite community</p>
<ul>
<li>
<a href="https://github.com/vitejs/vite" target="_blank">
<svg className="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#github-icon"></use>
</svg>
GitHub
</a>
</li>
<li>
<a href="https://chat.vite.dev/" target="_blank">
<svg className="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#discord-icon"></use>
</svg>
Discord
</a>
</li>
<li>
<a href="https://x.com/vite_js" target="_blank">
<svg className="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#x-icon"></use>
</svg>
X.com
</a>
</li>
<li>
<a href="https://bsky.app/profile/vite.dev" target="_blank">
<svg className="button-icon" role="presentation" aria-hidden="true">
<use href="/icons.svg#bluesky-icon"></use>
</svg>
Bluesky
</a>
</li>
</ul>
</div>
</section>
return (
<main style={{ padding: '0 24px' }}>
<h1>Spill</h1>
<SearchBar value={input} onChange={setInput} />
<h2>Søkeresultater</h2>

{/* Bytter mellom visning 1 (liste) og visning 2 (rutenett) */}
<div role="group" aria-label="Visning">
<button type="button" aria-pressed={view === 'list'} onClick={() => setView('list')}>
Liste
</button>
<button type="button" aria-pressed={view === 'grid'} onClick={() => setView('grid')}>
Rutenett
</button>
</div>

<div className="ticks"></div>
<section id="spacer"></section>
</>
<GameList
games={games}
view={view}
favorites={favorites}
onToggleFavorite={toggleFavorite}
/>
</main>
);
}

Expand Down
Loading
Loading