From e3435d2c5e26a351686e683106275fa2a7048ee9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Aanundsson=20Berdal?= Date: Wed, 14 Oct 2026 18:35:59 +0200 Subject: [PATCH 1/2] Prepare VM deployment and end-to-end test setup (#7) --- .gitignore | 5 + deploy/install-runtime.sh | 70 +++++++++++ deploy/setup-database.sh | 85 +++++++++++++ deploy/setup-test/index.html | 15 +++ deploy/setup-test/server.mjs | 42 +++++++ deploy/spillkveld-backend.service | 24 ++++ deploy/spillkveld-setup-test.service | 22 ++++ deploy/spillkveld.conf | 11 ++ docs/deployment.md | 172 +++++++++++++++++++++++++++ e2e/catalog.e2e.ts | 34 ++++++ playwright.config.ts | 15 +++ 11 files changed, 495 insertions(+) create mode 100644 deploy/install-runtime.sh create mode 100644 deploy/setup-database.sh create mode 100644 deploy/setup-test/index.html create mode 100644 deploy/setup-test/server.mjs create mode 100644 deploy/spillkveld-backend.service create mode 100644 deploy/spillkveld-setup-test.service create mode 100644 deploy/spillkveld.conf create mode 100644 docs/deployment.md create mode 100644 e2e/catalog.e2e.ts create mode 100644 playwright.config.ts diff --git a/.gitignore b/.gitignore index d6e42ef..22519f7 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,8 @@ coverage/ .vite/ *.local *.tsbuildinfo +playwright-report/ +test-results/ +.env +.env.* +!.env.example diff --git a/deploy/install-runtime.sh b/deploy/install-runtime.sh new file mode 100644 index 0000000..2bd15cc --- /dev/null +++ b/deploy/install-runtime.sh @@ -0,0 +1,70 @@ +#!/usr/bin/env bash +set -euo pipefail + +if [[ $EUID -ne 0 ]]; then + echo 'Run with sudo: sudo bash deploy/install-runtime.sh' >&2 + exit 1 +fi + +script_dir=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +[[ -s /etc/spillkveld/backend.env ]] || { echo 'Run setup-database.sh first.' >&2; exit 1; } +[[ $(curl -s -o /dev/null -w '%{http_code}' http://localhost/project1/) == 200 ]] + +if systemctl is-active --quiet spillkveld-backend.service; then + echo 'The real backend is running; refusing to replace it with the test server.' >&2 + exit 1 +fi +if ss -H -ltn 'sport = :3001' | grep -q .; then + systemctl is-active --quiet spillkveld-setup-test.service || { + echo 'Port 3001 is occupied by another process.' >&2 + exit 1 + } +fi + +if id spillkveld >/dev/null 2>&1; then + [[ $(getent passwd spillkveld | cut -d: -f7) == /usr/sbin/nologin ]] || { + echo 'An unexpected spillkveld system account already exists.' >&2 + exit 1 + } +else + useradd --system --user-group --home-dir /opt/spillkveld --no-create-home --shell /usr/sbin/nologin spillkveld +fi + +install -d -m 755 /opt/spillkveld/setup-test /var/www/html/project2 +install -m 644 "$script_dir/setup-test/server.mjs" /opt/spillkveld/setup-test/server.mjs +if [[ ! -e /var/www/html/project2/index.html ]]; then + install -m 644 "$script_dir/setup-test/index.html" /var/www/html/project2/index.html +fi +install -m 644 "$script_dir/spillkveld-backend.service" /etc/systemd/system/spillkveld-backend.service +install -m 644 "$script_dir/spillkveld-setup-test.service" /etc/systemd/system/spillkveld-setup-test.service +systemd-analyze verify /etc/systemd/system/spillkveld-backend.service /etc/systemd/system/spillkveld-setup-test.service + +configuration=/etc/apache2/conf-available/spillkveld.conf +if [[ -e $configuration ]] && ! cmp -s "$configuration" "$script_dir/spillkveld.conf"; then + echo 'Existing Apache configuration differs; inspect it before replacing it.' >&2 + exit 1 +fi +was_enabled=false +[[ ! -e /etc/apache2/conf-enabled/spillkveld.conf ]] || was_enabled=true +install -m 644 "$script_dir/spillkveld.conf" "$configuration" +a2enmod proxy proxy_http +a2enconf spillkveld +if ! apache2ctl configtest; then + if [[ $was_enabled == false ]]; then a2disconf spillkveld; fi + echo 'Apache configuration failed validation; Apache has not been reloaded.' >&2 + exit 1 +fi + +systemctl daemon-reload +systemctl enable postgresql +systemctl enable --now spillkveld-setup-test.service +curl --fail --silent --retry 5 --retry-connrefused --retry-delay 1 http://127.0.0.1:3001/health +systemctl reload apache2 + +[[ $(curl -s -o /dev/null -w '%{http_code}' http://localhost/project1/) == 200 ]] +[[ $(curl -s -o /dev/null -w '%{http_code}' http://localhost/project2/) == 200 ]] +[[ $(curl -s -o /dev/null -w '%{http_code}' -X POST http://localhost/project2/graphql) == 503 ]] +curl --silent -X POST http://localhost/project2/graphql | + node -e 'let body=""; process.stdin.on("data", c => body += c); process.stdin.on("end", () => { if (JSON.parse(body).setupOnly !== true) process.exit(1); });' +echo +echo 'Runtime setup verified. The temporary server is running; the real backend is not deployed.' diff --git a/deploy/setup-database.sh b/deploy/setup-database.sh new file mode 100644 index 0000000..b6d5f9a --- /dev/null +++ b/deploy/setup-database.sh @@ -0,0 +1,85 @@ +#!/usr/bin/env bash +set -euo pipefail +umask 077 + +if [[ $EUID -ne 0 ]]; then + echo 'Run with sudo: sudo bash setup-database.sh' >&2 + exit 1 +fi + +for command in psql createdb runuser node openssl; do + command -v "$command" >/dev/null +done + +admin_sql() { + runuser -u postgres -- psql -X -v ON_ERROR_STOP=1 -At -d postgres -c "$1" +} + +environment_file=/etc/spillkveld/backend.env +role_exists=$(admin_sql "SELECT EXISTS (SELECT FROM pg_roles WHERE rolname = 'spillkveld_app')") +database_exists=$(admin_sql "SELECT EXISTS (SELECT FROM pg_database WHERE datname = 'spillkveld')") + +if [[ -e $environment_file ]]; then + if [[ -L $environment_file || ! -f $environment_file || $(stat -c %u "$environment_file") != 0 ]]; then + echo 'Expected a regular root-owned backend.env; refusing to change it.' >&2 + exit 1 + fi + database_url=$(sed -n 's/^DATABASE_URL=//p' "$environment_file") + if [[ $database_url =~ ^postgresql://spillkveld_app:([a-f0-9]{64})@127\.0\.0\.1:5432/spillkveld$ ]]; then + password=${BASH_REMATCH[1]} + else + echo 'Existing DATABASE_URL does not match this setup; refusing to overwrite it.' >&2 + exit 1 + fi +else + if [[ $role_exists == t || $database_exists == t ]]; then + echo 'Database or role already exists without backend.env; inspect it before continuing.' >&2 + exit 1 + fi + password=$(openssl rand -hex 32) +fi + +if [[ $role_exists == t ]]; then + flags=$(admin_sql "SELECT rolcanlogin AND NOT rolsuper AND NOT rolcreatedb AND NOT rolcreaterole AND NOT rolreplication AND NOT rolbypassrls AND NOT EXISTS (SELECT FROM pg_auth_members WHERE member = pg_roles.oid) FROM pg_roles WHERE rolname = 'spillkveld_app'") + [[ $flags == t ]] || { echo 'Existing role has unexpected privileges.' >&2; exit 1; } +fi +if [[ $database_exists == t ]]; then + owner=$(admin_sql "SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname = 'spillkveld'") + [[ $owner == spillkveld_app ]] || { echo 'Existing database has an unexpected owner.' >&2; exit 1; } +fi + +# Save credentials before creating resources, so a failed run can be resumed. +install -d -m 750 -o root -g root /etc/spillkveld +if [[ ! -e $environment_file ]]; then + temporary_file=$(mktemp /etc/spillkveld/backend.env.XXXXXX) + trap 'rm -f -- "${temporary_file:-}"' EXIT + printf 'DATABASE_URL=postgresql://spillkveld_app:%s@127.0.0.1:5432/spillkveld\nPORT=3001\nHOST=127.0.0.1\n' "$password" > "$temporary_file" + mv -- "$temporary_file" "$environment_file" + trap - EXIT +fi +chmod 600 "$environment_file" + +if [[ $role_exists == f ]]; then + # Send a SCRAM verifier to PostgreSQL, not the plain-text password. + verifier=$(printf '%s' "$password" | node --input-type=module -e ' + import { readFileSync } from "node:fs"; + import { randomBytes, pbkdf2Sync, createHmac, createHash } from "node:crypto"; + const password = readFileSync(0, "utf8"); + const salt = randomBytes(16); + const salted = pbkdf2Sync(password, salt, 4096, 32, "sha256"); + const client = createHmac("sha256", salted).update("Client Key").digest(); + const stored = createHash("sha256").update(client).digest("base64"); + const server = createHmac("sha256", salted).update("Server Key").digest("base64"); + process.stdout.write(`SCRAM-SHA-256$4096:${salt.toString("base64")}$${stored}:${server}`); + ') + printf "CREATE ROLE spillkveld_app LOGIN NOSUPERUSER NOCREATEDB NOCREATEROLE NOREPLICATION NOBYPASSRLS PASSWORD '%s';\n" "$verifier" | + runuser -u postgres -- psql -X -v ON_ERROR_STOP=1 -d postgres +fi + +if [[ $database_exists == f ]]; then + runuser -u postgres -- createdb --owner=spillkveld_app spillkveld +fi + +PGPASSWORD="$password" psql -X -v ON_ERROR_STOP=1 --host=127.0.0.1 --username=spillkveld_app --dbname=spillkveld \ + -c 'SELECT current_user, current_database();' +echo 'Database ready. Credentials are in /etc/spillkveld/backend.env (root-only).' diff --git a/deploy/setup-test/index.html b/deploy/setup-test/index.html new file mode 100644 index 0000000..1672505 --- /dev/null +++ b/deploy/setup-test/index.html @@ -0,0 +1,15 @@ + + + + + + Spillkveld – driftsoppsett under arbeid + + +
+

Spillkveld – driftsoppsett under arbeid

+

Dette er en midlertidig testside for publisering på VM.

+

Den ferdige appen og GraphQL-backenden er ikke publisert ennå.

+
+ + diff --git a/deploy/setup-test/server.mjs b/deploy/setup-test/server.mjs new file mode 100644 index 0000000..a4cde51 --- /dev/null +++ b/deploy/setup-test/server.mjs @@ -0,0 +1,42 @@ +import { createServer } from 'node:http' +import process from 'node:process' +import console from 'node:console' + +const host = process.env.HOST || '127.0.0.1' +const port = Number(process.env.PORT || 3001) + +const server = createServer((request, response) => { + request.resume() + response.setHeader('Content-Type', 'application/json; charset=utf-8') + response.setHeader('Cache-Control', 'no-store') + + if (request.method === 'GET' && request.url === '/health') { + response.end(JSON.stringify({ status: 'setup-only' })) + return + } + + if (request.url === '/graphql') { + response.writeHead(503) + response.end( + JSON.stringify({ + errors: [ + { + message: + 'Temporary deployment test server. The GraphQL backend is not deployed yet.', + }, + ], + setupOnly: true, + }), + ) + return + } + + response.writeHead(404) + response.end(JSON.stringify({ error: 'Not found', setupOnly: true })) +}) + +server.listen(port, host, () => { + console.log(`Temporary deployment test listening on ${host}:${port}`) +}) + +process.on('SIGTERM', () => server.close()) diff --git a/deploy/spillkveld-backend.service b/deploy/spillkveld-backend.service new file mode 100644 index 0000000..ed68e4f --- /dev/null +++ b/deploy/spillkveld-backend.service @@ -0,0 +1,24 @@ +[Unit] +Description=Spillkveld GraphQL backend +After=network.target postgresql.service +After=spillkveld-setup-test.service +Wants=postgresql.service +ConditionPathExists=/opt/spillkveld/server/package.json + +[Service] +Type=simple +User=spillkveld +Group=spillkveld +WorkingDirectory=/opt/spillkveld/server +Environment=NODE_ENV=production +EnvironmentFile=/etc/spillkveld/backend.env +ExecStart=/usr/bin/npm start +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ProtectHome=true + +[Install] +WantedBy=multi-user.target diff --git a/deploy/spillkveld-setup-test.service b/deploy/spillkveld-setup-test.service new file mode 100644 index 0000000..25d0b82 --- /dev/null +++ b/deploy/spillkveld-setup-test.service @@ -0,0 +1,22 @@ +[Unit] +Description=Spillkveld temporary deployment test (not the GraphQL backend) +After=network.target +Conflicts=spillkveld-backend.service + +[Service] +Type=simple +User=spillkveld +Group=spillkveld +WorkingDirectory=/opt/spillkveld/setup-test +Environment=HOST=127.0.0.1 +Environment=PORT=3001 +ExecStart=/usr/bin/node server.mjs +Restart=on-failure +RestartSec=5 +NoNewPrivileges=true +PrivateTmp=true +ProtectSystem=full +ProtectHome=true + +[Install] +WantedBy=multi-user.target diff --git a/deploy/spillkveld.conf b/deploy/spillkveld.conf new file mode 100644 index 0000000..ea75a4c --- /dev/null +++ b/deploy/spillkveld.conf @@ -0,0 +1,11 @@ +ProxyRequests Off +ProxyPass "/project2/graphql" "http://127.0.0.1:3001/graphql" connectiontimeout=5 timeout=30 +ProxyPassReverse "/project2/graphql" "http://127.0.0.1:3001/graphql" + +Alias "/project2/" "/var/www/html/project2/" + + Options -Indexes + AllowOverride None + Require all granted + DirectoryIndex index.html + diff --git a/docs/deployment.md b/docs/deployment.md new file mode 100644 index 0000000..b782104 --- /dev/null +++ b/docs/deployment.md @@ -0,0 +1,172 @@ +# Drift og publisering + +## Status + +VM: `it2810-32.idi.ntnu.no`. SSH: `ssh bjoabe@it2810-32.idi.ntnu.no`. +Bruk NTNU-nettet eller VPN utenfor campus. + +Kontrollert og installert på VM: + +- Apache kjører og er aktivert for automatisk oppstart. +- Prosjekt 1 ligger i `/var/www/html/project1` og svarer med HTTP 200. +- Node.js 22.22.1 og npm 9.2.0 er installert. +- PostgreSQL 18.6 kjører på `127.0.0.1:5432`. + +Databasen `spillkveld` og brukeren `spillkveld_app` er opprettet. Skriptets +innloggingstest mot databasen passerer. Skjema og spilldata er ikke lagt inn. +Apache-konfigurasjon og tjenestefiler er installert og kontrollert på VM. +Testtjenesten kjører på `127.0.0.1:3001` og er aktivert for automatisk oppstart. +`/project2/` viser den midlertidige testsiden med HTTP 200, og Apache videresender +`/project2/graphql` til testserveren med forventet HTTP 503 og `setupOnly: true`. +Prosjekt 1 svarer fortsatt med HTTP 200. Den ekte backenden og frontend er +ikke publisert, og automatisk oppstart etter en faktisk VM-omstart er ikke testet. + +## Database + +Kjør `deploy/setup-database.sh` på VM med administratorrettigheter: + +```bash +sudo bash setup-database.sh +``` + +Skriptet oppretter databasen `spillkveld` og innloggingsbrukeren +`spillkveld_app`. Brukeren eier denne databasen, men får ikke rettigheter +til å administrere PostgreSQL, opprette databaser eller opprette andre brukere. + +Et tilfeldig passord genereres på VM. `DATABASE_URL`, `PORT=3001` og +`HOST=127.0.0.1` lagres i `/etc/spillkveld/backend.env`, som bare root kan +lese. Den kommende backend-tjenesten skal lese denne filen gjennom systemd. +Passord og den faktiske miljøfilen skal ikke kopieres inn i repoet. + +Skriptet kontrollerer innlogging med applikasjonsbrukeren. Det kan kjøres +på nytt med samme miljøfil uten å bytte passord eller tømme databasen. +Hvis det finnes et annet oppsett med samme navn, stopper det for kontroll. + +Skjema og eksempeldata lastes senere fra `server/db/schema.sql` og +`server/db/seed.sql`, med samme databasebruker som backend. + +## Apache og tjenester + +Last opp hele `deploy/`-mappen og kjør fra mappen over den på VM: + +```bash +sudo bash deploy/install-runtime.sh +``` + +Oppsettet oppretter systembrukeren `spillkveld` uten interaktiv innlogging og +installerer følgende filer: + +- `/etc/apache2/conf-available/spillkveld.conf` +- `/etc/systemd/system/spillkveld-backend.service` +- `/etc/systemd/system/spillkveld-setup-test.service` + +Backend-tjenesten leser `/etc/spillkveld/backend.env` og kjører `npm start` +fra `/opt/spillkveld/server`. Den aktiveres for automatisk oppstart når den +ekte backenden er lagt inn og bygget. Inntil da er bare testtjenesten aktivert. + +Foreløpig kjører en separat testserver på `127.0.0.1:3001`. Den gir +`setup-only` fra `/health` og HTTP 503 med `setupOnly: true` fra `/graphql`. +Dette kontrollerer prosessdrift og Apache-proxyen, ikke database–GraphQL-flyten. +Testsiden under `/project2/` er tydelig merket som midlertidig. + +Skriptet kontrollerer Apache-konfigurasjonen før reload, og bekrefter at +prosjekt 1 og testsiden svarer med HTTP 200. Det erstatter ikke en allerede +publisert frontend eller en kjørende ekte backend. + +## Når backend og databasefiler er klare + +1. Legg backend-koden i `/opt/spillkveld/server`. Installer og bygg med de + dokumenterte npm-kommandoene. Systembrukeren `spillkveld` må kunne lese + programfilene. Backend skal bruke `DATABASE_URL`, `HOST` og `PORT` fra miljøet. +2. Last databaseskjema og eksempeldata gjennom applikasjonsbrukerens tilkobling. + Kjør skjema før seed. Ikke bruk PostgreSQL-administrator som backend-bruker. +3. Stopp testserveren før den ekte backenden startes: + + ```bash + sudo systemctl disable --now spillkveld-setup-test.service + sudo systemctl enable --now spillkveld-backend.service + curl --fail http://127.0.0.1:3001/health + ``` + +4. Bygg og publiser frontend med verdiene nedenfor. Kontroller den første ekte + flyten: databasespørring → GraphQL → synlig spill i klienten. + +## Adresser og produksjonsbygg + +- Frontend: `/var/www/html/project2`, tilgjengelig under `/project2/`. +- Backend: `127.0.0.1:3001`, API under `/graphql`, kjørt som systemd-tjeneste. +- Apache skal videresende `/project2/graphql` til backend. +- Frontend bygges med Vite-base `/project2/` og + `VITE_GRAPHQL_URL=/project2/graphql`. +- Prosjekt 1 skal fortsette å fungere under `/project1/`. + +## Ende-til-ende-test + +Playwright-oppsettet er i `playwright.config.ts`. Testen i +`e2e/catalog.e2e.ts` søker etter Azul og åpner detaljer gjennom ekte GraphQL. +Testen bruker ikke mocker. Backend og database må derfor være klare før den +kan passere. Filendelsen `.e2e.ts` skiller den fra Vitest-testene. + +Installer først prosjektets avhengigheter med `npm ci`, deretter nettleseren: + +```bash +npx playwright install chromium +``` + +Mot en lokal klient og backend som allerede kjører: + +```bash +npm run test:e2e +``` + +Mot VM fra PowerShell på NTNU-nettet/VPN: + +```powershell +$env:PLAYWRIGHT_BASE_URL = 'http://it2810-32.idi.ntnu.no/project2/' +npm run test:e2e +``` + +`npm run test:e2e -- --list` kontrollerer at testen kan oppdages, men er ikke +en gjennomført integrasjonstest. Samlet README ferdigstilles når dokumentasjonen +for alle delene er tilgjengelig. + +## Kontroll og feilsøking + +```bash +node --version +npm --version +pg_isready -h 127.0.0.1 -p 5432 +pg_lsclusters +systemctl status apache2 +systemctl status spillkveld-setup-test +curl -I http://localhost/project1/ +curl -I http://localhost/project2/ +curl http://127.0.0.1:3001/health +curl -X POST http://localhost/project2/graphql +``` + +Etter publisering av ekte backend brukes følgende for status, logger og omstart: + +```bash +systemctl status spillkveld-backend +sudo journalctl -u spillkveld-backend -n 50 --no-pager +sudo systemctl restart spillkveld-backend +``` + +Ved Apache-feil: kjør `sudo apache2ctl configtest` før reload og se +`sudo journalctl -u apache2 -n 50 --no-pager`. HTTP 503 med `setupOnly: true` +er forventet så lenge testserveren kjører. Etter bytte til ekte backend skal +`/health` i stedet bekrefte databasetilkoblingen etter API-avtalen. + +## KI-erklæring + +Codex er brukt til å gjennomgå tidligere VM-oppsett, lage database- og +driftsoppsett, forberede Playwright-testen og skrive dokumentasjonen. +Installasjon og status er kontrollert med versjonskommandoer, PostgreSQLs +tilkoblingskontroll og HTTP-kall til prosjekt 1. Databaseoppsettet er kjørt +og bekreftet med innlogging som `spillkveld_app` i databasen `spillkveld`. +Runtime-oppsettet er installert og kontrollert med syntakssjekk, tjenestestatus +og HTTP-kall gjennom Apache. De 13 eksisterende testene, linting, formateringssjekk +og produksjonsbygg passerer. Playwright finner testen med `--list`, men testen +mot ekte backend og database er ikke kjørt ennå. Backend-integrasjon og faktisk +VM-omstart må kontrolleres når resten av løsningen er klar. diff --git a/e2e/catalog.e2e.ts b/e2e/catalog.e2e.ts new file mode 100644 index 0000000..3bc7eda --- /dev/null +++ b/e2e/catalog.e2e.ts @@ -0,0 +1,34 @@ +import { expect, test } from '@playwright/test' + +test('searches the real catalog and opens game details', async ({ page }) => { + const catalogResponse = page.waitForResponse((response) => { + const request = response.request() + return ( + request.method() === 'POST' && + new URL(response.url()).pathname.endsWith('/graphql') && + request.postDataJSON()?.operationName === 'GetGames' && + request.postDataJSON()?.variables?.input?.query === 'Azul' + ) + }) + + await page.goto('./') + await page.getByRole('searchbox', { name: 'Søk etter spill' }).fill('Azul') + const response = await catalogResponse + expect(response.ok()).toBeTruthy() + const body = await response.json() + expect(body.errors).toBeUndefined() + expect(body.data.games.items).toEqual( + expect.arrayContaining([ + expect.objectContaining({ id: 'azul', name: 'Azul' }), + ]), + ) + + await page.getByRole('button', { name: 'Azul', exact: true }).click() + const heading = page.getByRole('heading', { name: 'Azul', level: 1 }) + await expect(heading).toBeVisible() + await expect(heading).toBeFocused() + await page.getByRole('button', { name: /Tilbake til resultatene/ }).click() + await expect( + page.getByRole('searchbox', { name: 'Søk etter spill' }), + ).toHaveValue('Azul') +}) diff --git a/playwright.config.ts b/playwright.config.ts new file mode 100644 index 0000000..d7d6b06 --- /dev/null +++ b/playwright.config.ts @@ -0,0 +1,15 @@ +import { defineConfig, devices } from '@playwright/test' + +export default defineConfig({ + testDir: './e2e', + testMatch: '**/*.e2e.ts', + forbidOnly: !!process.env.CI, + retries: process.env.CI ? 2 : 0, + reporter: 'list', + use: { + baseURL: + process.env.PLAYWRIGHT_BASE_URL || 'http://127.0.0.1:5173/project2/', + trace: 'retain-on-failure', + }, + projects: [{ name: 'chromium', use: { ...devices['Desktop Chrome'] } }], +}) From 418bfb4b58304b77c3a64331108f305ca5ab25fa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bj=C3=B8rn=20Aanundsson=20Berdal?= Date: Fri, 16 Oct 2026 13:33:44 +0200 Subject: [PATCH 2/2] Complete VM deployment and consolidate documentation (#7) --- README.md | 210 +++++++++++++++++++-- deploy/publish-first-release.sh | 89 +++++++++ docs/backend.md | 133 -------------- docs/database.md | 82 --------- docs/deployment.md | 172 ------------------ docs/frontend.md | 313 -------------------------------- server/scripts/run-sql.mjs | 35 ++-- 7 files changed, 295 insertions(+), 739 deletions(-) create mode 100644 deploy/publish-first-release.sh delete mode 100644 docs/backend.md delete mode 100644 docs/database.md delete mode 100644 docs/deployment.md delete mode 100644 docs/frontend.md diff --git a/README.md b/README.md index 162a80c..84251a9 100644 --- a/README.md +++ b/README.md @@ -1,47 +1,217 @@ # Spillkveld -Spillkveld er en nettside som skal gjøre det enklere for venner og familier å finne brettspill som passer til antall personer og tiden de har. Dette er den første, kjørbare prototypen i IT2810. Den lar medstudenter prøve hovedideen før vi kobler på en backend og et større datasett. +Spillkveld hjelper venner og familier å finne brettspill som passer til antall personer og tiden de har. Applikasjonen er utviklet av gruppe T32 i IT2810. -## Kom i gang +**Prøv appen:** [Spillkveld på VM](http://it2810-32.idi.ntnu.no/project2/). Bruk NTNU-nettet eller NTNU VPN utenfor campus. -Du trenger Node.js 22 eller nyere og npm. Kjør følgende fra prosjektmappen: +**Repo:** [IT2810-H26/T32-Project-2](https://git.ntnu.no/IT2810-H26/T32-Project-2). + +## Funksjoner + +- Søk i navn og beskrivelse, og kombiner filtre for antall spillere, spilletid og kategori. +- Sorter etter navn eller spilletid, og bla gjennom seks spill per side. +- Åpne spilldetaljer og gå tilbake med søk og filtre bevart. +- Legg til og fjern spill i «Kveldens utvalg». Listen lagres i nettleseren og beholdes etter ny innlasting. +- Mobiltilpasset grensesnitt med tastaturbetjening, synlig fokus og meldinger ved lasting, feil og ingen treff. + +Spillene hentes fra **PostgreSQL gjennom GraphQL**. Søk, filtrering, sortering og sidevisning skjer i backend. + +**De 12 spillene er eksempeldata**, med forenklede beskrivelser, kategorier og spilletider. De kommer ikke fra et valgt eller offisielt brettspilldatasett. Symbolene er dekorative ikoner. + +## Lokal oppstart + +Du trenger Git, npm, Node.js 22.22 eller 24, og en kjørende PostgreSQL-installasjon. Løsningen er testet med PostgreSQL 17.11 og 18.6. + +### 1. Installer avhengigheter + +```bash +git clone https://git.ntnu.no/IT2810-H26/T32-Project-2.git +cd T32-Project-2 +npm ci +npm --prefix server ci +``` + +### 2. Opprett databaser + +Åpne `psql` som lokal PostgreSQL-administrator. På en ny installasjon: + +```sql +CREATE ROLE spillkveld_app LOGIN; +\password spillkveld_app +CREATE DATABASE spillkveld OWNER spillkveld_app; +CREATE DATABASE spillkveld_test OWNER spillkveld_app; +``` + +`\password` ber om et passord til den lokale databasebrukeren. Testdatabasen brukes bare til automatiske tester. + +### 3. Konfigurer og fyll databasen + +Kopier `server/.env.example` til `server/.env`, og sett inn ditt lokale databasepassord: + +```dotenv +DATABASE_URL=postgresql://spillkveld_app:DITT_LOKALE_PASSORD@127.0.0.1:5432/spillkveld +TEST_DATABASE_URL=postgresql://spillkveld_app:DITT_LOKALE_PASSORD@127.0.0.1:5432/spillkveld_test +PORT=3001 +HOST=127.0.0.1 +``` + +Spesialtegn i passordet må URL-kodes. Faktiske passord og `.env`-filer skal ikke legges i Git. + +Kjør fra prosjektroten: + +```bash +npm --prefix server run db:setup +npm --prefix server run db:seed +``` + +Seed kan kjøres flere ganger uten duplikater; eksisterende spill blir ikke oppdatert. + +### 4. Start appen + +Start backend: + +```bash +npm --prefix server run dev +``` + +Start frontend i en annen terminal, også fra prosjektroten: ```bash -npm install npm run dev ``` -Åpne adressen som Vite viser, vanligvis `http://localhost:5173`. +Åpne [http://localhost:5173/project2/](http://localhost:5173/project2/). La `VITE_GRAPHQL_URL` være tom ved lokal utvikling; Vite videresender `/graphql` til backend på port 3001. + +## Arkitektur + +Frontend bruker React, TypeScript og Vite. Apollo Client sender GraphQL-spørringer til en Node.js-backend med GraphQL Yoga. Backend bruker `pg` til å hente spill fra PostgreSQL. Apache serverer frontend og videresender API-kall på VM-en. + +| Mappe | Innhold | +| ----------------------- | ------------------------------------------------------------ | +| `src/` | Grensesnitt, Apollo-oppsett og frontend-tester | +| `server/src/` | GraphQL-skjema, validering og parameteriserte SQL-spørringer | +| `server/db/` | Tabellskjema og de 12 eksempelspillene | +| `server/tests/`, `e2e/` | Backend-tester og Playwright-test | +| `deploy/` | Apache, systemd og publiseringsskript | + +API-et har fire spørringer: `games` for katalogen, `game` for detaljer, `gamesByIds` for utvalgte spill og `categories` for kategorier. Backend beregner totalt antall treff før sidevisning og sikrer stabil sortering. Databasen krever gyldige spillergrenser, positiv spilletid og at nødvendige felt er fylt ut. Feltene er definert i `server/db/schema.sql` og `server/src/schema.ts`. + +## Tester og kodekvalitet -For å kontrollere prosjektet: +Kjør fra prosjektroten: ```bash npm test +npm run test:backend npm run lint +npm run format:check npm run build +npm --prefix server run build ``` -`npm run preview` viser produksjonsbygget lokalt etter `npm run build`. +Frontend-testene bruker mockede GraphQL-svar og trenger ingen database. Backend-testene bruker `TEST_DATABASE_URL` med `localhost` eller `127.0.0.1` og databasen `spillkveld_test`. **Testene tømmer spilltabellen i testdatabasen** og setter inn egne testdata. -## Dette fungerer nå +Lint kontrollerer både frontend og backend. Formateringskommandoen dekker frontend og Vite-konfigurasjonen; øvrige filer kan kontrolleres med: -- Søk etter ord i spillnavn eller beskrivelse. -- Filtre for antall spillere, maksimal spilletid og kategori. Filtrene kan kombineres. -- Sortering etter navn eller kortest spilletid, og sidevisning av søkeresultater. -- Detaljvisning med beskrivelse og praktisk informasjon. Søk og filtre beholdes når du går tilbake. -- «Kveldens utvalg»: legg til og fjern spill. Listen lagres i nettleserens lokale lagring på samme enhet. -- Mobiltilpasset grensesnitt med synlig tastaturfokus, tydelige feltnavn og melding ved null treff. +```bash +npx prettier --check "server/**/*.{ts,mjs,json}" "e2e/**/*.ts" playwright.config.ts README.md +``` + +### Ende-til-ende-test + +Installer nettleseren én gang med `npx playwright install chromium`. Med lokal frontend og backend i gang kjører du `npm run test:e2e`. Mot VM fra PowerShell: + +```powershell +$env:PLAYWRIGHT_BASE_URL = 'http://it2810-32.idi.ntnu.no/project2/' +npm run test:e2e +Remove-Item Env:PLAYWRIGHT_BASE_URL +``` + +Testen søker etter Azul, kontrollerer ekte GraphQL-svar, åpner detaljer og går tilbake med søket bevart. + +### Verifisert 16. oktober 2026 + +- Alle 13 frontend-tester og 22 backend-tester passerer. Backend-testene er også kjørt på VM med det felles databaseskjemaet. +- Lint, frontend-formatering og begge produksjonsbygg passerer. +- Ende-til-ende-testen passerer mot den publiserte appen, både før og etter VM-omstart. Appens hovedfunksjoner er også prøvd manuelt. +- Apache, PostgreSQL og backend startet automatisk etter omstart. Prosjekt 1 fungerer fortsatt. +- Seed kan kjøres flere ganger uten duplikater. Databasen avviser ugyldige spillergrenser, spilletid og manglende navn. + +## Bygg og drift på VM + +Frontend bygges med Vite-base `/project2/` og `VITE_GRAPHQL_URL=/project2/graphql`. Fra PowerShell i prosjektroten: + +```powershell +$env:VITE_GRAPHQL_URL = '/project2/graphql' +npm run build +Remove-Item Env:VITE_GRAPHQL_URL +npm --prefix server run build +``` + +| Del | Oppsett på `it2810-32.idi.ntnu.no` | +| ---------- | -------------------------------------------------------------------------------- | +| Frontend | `dist/` publiseres til `/var/www/html/project2` | +| Backend | `/opt/spillkveld/server`, port `127.0.0.1:3001` | +| API-proxy | Apache videresender `/project2/graphql` til backend `/graphql` | +| PostgreSQL | Installert direkte uten Docker, database `spillkveld` på `127.0.0.1:5432` | +| Miljøfil | `/etc/spillkveld/backend.env`, kun lesbar for root | +| Tjeneste | `spillkveld-backend.service`, kjører `npm start` som systembrukeren `spillkveld` | + +VM-en bruker Node.js 22.22.1, npm 9.2.0, PostgreSQL 18.6 og Apache 2.4.66. Systemd leser `DATABASE_URL`, `HOST` og `PORT` fra miljøfilen. Databasebrukeren `spillkveld_app` eier databasen uten administratorrettigheter. + +Oppsettet forutsetter at Node.js, npm, PostgreSQL og Apache er installert på VM-en, og at prosjekt 1 svarer under `/project1/`. Kjør de to første kommandoene fra mappen som inneholder `deploy/`. + +Førstegangsoppsettet består av tre steg: -**Spillene er statiske eksempeldata.** Beskrivelser, kategorier og spilletider er forenklet for å demonstrere prototypen. De er ikke hentet fra et valgt eller offisielt brettspilldatasett. Illustrasjonene er dekorative symboler, ikke spillbilder. +1. Kjør `sudo bash deploy/setup-database.sh` for å opprette database, bruker og miljøfil. +2. Kjør `sudo bash deploy/install-runtime.sh` for å installere Apache- og tjenesteoppsett. +3. Klargjør en utgivelsesmappe med backend under `server/`, bygget frontend under `dist/` og en kopi av `deploy/publish-first-release.sh` direkte i mappen. Installer, bygg og kontroller backend der, og kjør `sudo bash publish-first-release.sh`. Skriptet er tilpasset gruppens VM og brukeren `bjoabe`. -## Videre arbeid +Førstegangspubliseringen er gjennomført. Skriptet skal ikke kjøres på nytt på den publiserte appen. Den midlertidige testtjenesten er deaktivert. Ved senere oppdateringer bygges og testes en ny kopi før driftsfilene erstattes og backend startes på nytt. Behold miljøfilen og eksisterende data. -Vi skal velge og vurdere et brettspilldatasett, og bygge en Node.js- og TypeScript-backend med GraphQL og egen database på gruppens virtuelle maskin. Da skal søk, filtre, sortering og sidevisning gjelde hele katalogen, og klienten hente én resultatside om gangen. Backend skal også lagre «Kveldens utvalg». Vi vil vurdere bilder og vanskelighetsgrad når datakilde og rettigheter er avklart. «Hjelp meg å velge» kan bli en senere utvidelse. Kart, vurderinger og kommentarer er ikke del av denne prototypen. +Etter SSH-innlogging på VM kan drift kontrolleres med: + +```bash +systemctl status apache2 postgresql spillkveld-backend +curl --fail http://127.0.0.1:3001/health +sudo journalctl -u spillkveld-backend -n 50 --no-pager +sudo systemctl restart spillkveld-backend +``` + +`/health` gir HTTP 200 med `{"status":"ok","database":"ok"}` når databasen er tilgjengelig, ellers HTTP 503. Ved Apache-endringer kontrolleres konfigurasjonen med `sudo apache2ctl configtest` før reload. + +## Begrensninger og videre arbeid + +Katalogen har bare de 12 eksempelspillene, og detaljvisningen viser foreløpig omtrent samme informasjon som kortet. «Kveldens utvalg» synkroniseres ikke mellom nettlesere eller enheter. Én ende-til-ende-test dekker søk og detaljer; den dekker ikke alle funksjoner og skjermstørrelser. VM-en bruker HTTP og krever NTNU-nett/VPN. + +Et større datasett, bilder og «Hjelp meg å velge» kan vurderes videre. Kart, vurderinger og kommentarer er ikke implementert. ## Arbeid i repoet -Tekstfiler skal bruke LF-linjeskift på alle operativsystemer. `.gitattributes` styrer dette i Git, og `.editorconfig` gir samme regel til editorer og Prettier. Bruk støtte for EditorConfig i editoren din. +Tekstfiler skal bruke LF-linjeskift, styrt av `.gitattributes` og `.editorconfig`. Arbeid knyttes til en issue og gjøres på egen branch, for eksempel `feature/7-vm-deployment`. Issue- og PR-tekster og commitmeldinger skrives på engelsk; commitmeldinger viser til relevant issue. + +`main` er beskyttet. Endringer leveres som pull request og må godkjennes av et annet medlem av `IT2810-H26-T32`; godkjenning fra ansatte teller ikke. Svar på og løs alle review-kommentarer. Nye commits fjerner tidligere godkjenning, så be om ny gjennomgang etter endringer. + +Prosjektet bygger på [prosjektbeskrivelsen](https://docs.google.com/document/d/18swVXIU3Pm7DdFzoIUmIShoD_kerodH1R3CQX6Zy5qw/edit). + +## KI-erklæringer + +### Kamilla – database + +ChatGPT ble brukt til å forklare PostgreSQL-oppsett, bistå med utkast til SQL-skjema og seed-fil og gi testinstruksjoner. SQL-filene ble kjørt, og resultatene ble kontrollert lokalt. + +### Marius – backend + +ChatGPT ble brukt til stegvis veiledning og kodeforslag for backend-konfigurasjon, databasetilgang, GraphQL-operasjoner, API-tester og dokumentasjon. + +Verifiseringen av implementasjonen er beskrevet under tester. Ved backendleveransen var de gjenstående integrasjonskontrollene ennå ikke gjennomført. + +### Andreas – frontend + +ChatGPT ble brukt som støtte under frontend-utviklingen, hovedsakelig til å diskutere løsningsforslag, feilsøke, gjennomgå kode og foreslå forbedringer. + +KI ble også brukt til hjelp med React, TypeScript, Apollo Client, GraphQL-integrasjon, automatiske tester og dokumentasjon. Forslagene ble gjennomgått, tilpasset ved behov og testet for å kontrollere at de fungerte som tiltenkt. Alle endringer ble kontrollert med automatiske tester, ESLint og produksjonsbygg. -`main` er beskyttet. Arbeid på en egen branch og åpne en pull request når arbeidet skal gjennomgås. Før den kan merges, må et medlem av `IT2810-H26-T32` godkjenne den; du kan ikke godkjenne din egen pull request, og godkjenning fra ansatte teller ikke. Svar på og løs alle review-kommentarer. Nye commits etter en godkjenning fjerner godkjenningen, så be om ny gjennomgang hvis du gjør endringer. +### Bjørn – prototype, drift og integrasjon -Denne prototypen følger [prosjektbeskrivelsen](https://docs.google.com/document/d/18swVXIU3Pm7DdFzoIUmIShoD_kerodH1R3CQX6Zy5qw/edit) og [issue #1](https://git.ntnu.no/IT2810-H26/T32-Project-2/issues/1). +Codex ble brukt til hjelp med prototypen, kodegjennomgang, VM-oppsett, publisering, Playwright-test og samlet dokumentasjon. Resultatet ble kontrollert med tester, lint, produksjonsbygg, HTTP-/GraphQL-kall og manuell utprøving. Automatisk oppstart og ende-til-ende-test ble også kontrollert etter VM-omstart. diff --git a/deploy/publish-first-release.sh b/deploy/publish-first-release.sh new file mode 100644 index 0000000..070b659 --- /dev/null +++ b/deploy/publish-first-release.sh @@ -0,0 +1,89 @@ +#!/usr/bin/env bash +# First publication only. Build and test the staged release before running this. +set -euo pipefail +umask 077 + +[[ $EUID -eq 0 ]] || { echo 'Run this script with sudo.' >&2; exit 1; } +release=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd) +environment_file=/etc/spillkveld/backend.env +[[ -f $environment_file && ! -L $environment_file ]] +[[ $(stat -c %u "$environment_file") == 0 ]] +[[ ! -e /opt/spillkveld/server ]] +! systemctl is-active --quiet spillkveld-backend.service +systemctl is-active --quiet spillkveld-setup-test.service +[[ -s $release/server/dist/index.js && -s $release/dist/index.html ]] +[[ $(curl -s -o /dev/null -w '%{http_code}' http://localhost/project1/) == 200 ]] +apache2ctl configtest + +set -a +# This root-owned file was generated by setup-database.sh and is never printed. +source "$environment_file" +set +a +[[ $DATABASE_URL =~ ^postgresql://spillkveld_app:[a-f0-9]{64}@127\.0\.0\.1:5432/spillkveld$ ]] +export TEST_DATABASE_URL="${DATABASE_URL%/spillkveld}/spillkveld_test" + +test_owner=$(runuser -u postgres -- psql -X -At -d postgres -c "SELECT pg_get_userbyid(datdba) FROM pg_database WHERE datname = 'spillkveld_test'") +if [[ -z $test_owner ]]; then + runuser -u postgres -- createdb --owner=spillkveld_app spillkveld_test +elif [[ $test_owner != spillkveld_app ]]; then + echo 'Unexpected test database owner; stopping.' >&2 + exit 1 +fi + +export PGHOST=127.0.0.1 PGPORT=5432 PGUSER=spillkveld_app +export PGPASSWORD="${DATABASE_URL#postgresql://spillkveld_app:}" +PGPASSWORD="${PGPASSWORD%@127.0.0.1:5432/spillkveld}" + +# Validate the shared schema and seed in the separate test database first. +( + cd "$release/server" + DATABASE_URL="$TEST_DATABASE_URL" runuser -u bjoabe -- npm run db:setup + DATABASE_URL="$TEST_DATABASE_URL" runuser -u bjoabe -- npm run db:seed + runuser -u bjoabe -- npm test +) + +# Production SQL runs with the application role, never the PostgreSQL admin role. +( + cd "$release/server" + runuser -u bjoabe -- npm run db:setup + runuser -u bjoabe -- npm run db:seed +) +count=$(PGDATABASE=spillkveld runuser -u bjoabe -- psql -X -At -v ON_ERROR_STOP=1 -c 'SELECT COUNT(*) FROM games') +[[ $count == 12 ]] || { echo 'Expected exactly 12 example games; stopping.' >&2; exit 1; } + +backup="/var/www/html/project2.before-first-release-$(date +%Y%m%d%H%M%S)" +[[ ! -e $backup ]] +cp -a -- "$release/server" /opt/spillkveld/server +chown -R root:root /opt/spillkveld/server +chmod -R a+rX /opt/spillkveld/server + +frontend_swapped=false +rollback() { + echo 'Publication failed; restoring the temporary test service.' >&2 + systemctl disable --now spillkveld-backend.service || true + systemctl enable --now spillkveld-setup-test.service || true + if [[ $frontend_swapped == true ]]; then + mv -- /var/www/html/project2 "${backup}.failed" + mv -- "$backup" /var/www/html/project2 + fi +} +trap rollback ERR +systemctl disable --now spillkveld-setup-test.service +systemctl enable --now spillkveld-backend.service +curl --fail --silent --retry 10 --retry-connrefused --retry-delay 1 http://127.0.0.1:3001/health + +mv -- /var/www/html/project2 "$backup" +frontend_swapped=true +cp -a -- "$release/dist" /var/www/html/project2 +chown -R root:root /var/www/html/project2 +chmod -R a+rX /var/www/html/project2 +curl --fail --silent http://localhost/project2/ >/dev/null +curl --fail --silent -H 'Content-Type: application/json' \ + --data '{"query":"{ games(input: {query: \"Azul\"}) {totalCount items {id name}} }"}' \ + http://localhost/project2/graphql | + node -e 'let body=""; process.stdin.on("data", c => body += c); process.stdin.on("end", () => { const result=JSON.parse(body); if (result.errors || !result.data?.games?.items?.some(g => g.id === "azul")) process.exit(1); console.log("Verified Azul through Apache and GraphQL."); });' +[[ $(curl -s -o /dev/null -w '%{http_code}' http://localhost/project1/) == 200 ]] +trap - ERR +echo +echo 'First release published. Database tests passed and project1 still responds.' +echo 'Open http://it2810-32.idi.ntnu.no/project2/ to try the app.' diff --git a/docs/backend.md b/docs/backend.md deleted file mode 100644 index 402b41a..0000000 --- a/docs/backend.md +++ /dev/null @@ -1,133 +0,0 @@ -# Backend - -The backend uses Node.js, TypeScript, GraphQL Yoga, and PostgreSQL through `pg`. - -It provides four read operations: `games`, `game`, `gamesByIds`, and `categories`. Search, filtering, sorting, and pagination run in PostgreSQL. Database fields are mapped from snake_case to camelCase for the API. - -## Requirements - -- Node.js 22 or newer and npm. -- A running PostgreSQL instance. -- A development database. -- A separate local database named `spillkveld_test` for automated tests. -- `psql` on PATH when using the database setup commands. - -## Installation - -Run the following from `server/`: - -```bash -npm ci -``` - -Create `.env` using `.env.example` as a template. Set the actual database connection values locally. - -- `DATABASE_URL`: development or deployment database. -- `TEST_DATABASE_URL`: local `spillkveld_test` database. -- `PORT`: normally `3001`. -- `HOST`: normally `127.0.0.1`. - -Do not commit actual credentials or `.env`. - -## Database setup - -Person A provides `db/schema.sql` and `db/seed.sql`. - -Once these files are available, run from `server/`: - -```bash -npm run db:setup -npm run db:seed -``` - -These commands use `DATABASE_URL`. - -Until the shared SQL files are available, local backend development uses the agreed table schema and temporary development data. Automated tests create their own table and fixtures in `spillkveld_test`. - -## Running - -For development: - -```bash -npm run dev -``` - -For production: - -```bash -npm run build -npm start -``` - -Default local endpoints: - -- GraphQL: `http://127.0.0.1:3001/graphql` -- Health: `http://127.0.0.1:3001/health` - -## Testing - -Create `spillkveld_test` with a database user that can create and modify its tables. Configure `TEST_DATABASE_URL`. - -Run from `server/`: - -```bash -npm test -npm run lint -npm run build -``` - -Tests clear the games table in the test database and load fixed fixtures. The test database must be dedicated to automated testing. - -The API tests exercise GraphQL against PostgreSQL, including search, filters, pagination, sorting, game lookup, categories, ID handling, and invalid input. - -The health endpoint is checked separately. - -## Error handling - -Invalid input returns GraphQL errors. An unknown game ID returns null. - -The health endpoint checks the database and returns: - -- HTTP 200 with `{"status":"ok","database":"ok"}` on success. -- HTTP 503 with `{"status":"error","database":"unavailable"}` on failure. - -## Verification - -Complete this section with actual results before review: - -## Verification - -- API tests: 22 tests passed against the local PostgreSQL test database. -- Lint: `npm run lint` passed without errors. -- Production build: `npm run build` completed successfully. -- Production start: `npm start` successfully started the compiled backend on port 3001. -- Health endpoint with available database: `GET /health` returned HTTP 200 with `{"status":"ok","database":"ok"}`. -- Health endpoint with unavailable database: Not yet verified. -- Manual GraphQL request: Not yet verified. -- Integration with person A's SQL files, the frontend, and the VM: Pending. - -## Verification after review changes - -- Frontend tests: 13 tests passed using `npm test` from the repository root. Only frontend tests were discovered. -- Backend tests: 22 tests passed using `npm run test:backend`, with the separate backend configuration and local PostgreSQL test database. -- Frontend production build: `npm run build` passed. -- Backend production build: `npm --prefix server run build` passed. -- Root lint: `npm run lint` passed for both frontend and backend after the production builds. Backend lint includes the database script. -- Formatting: The explicit Prettier check passed for the updated configuration, backend code, tests, scripts, and documentation. - -The checks were run using Node.js v25.8.1. Installation reported an engine compatibility warning for frontend Vitest 5.0.4, although the tests and builds completed successfully. - -Integration with the shared SQL files and the VM remains pending. - -## Remaining integration work - -- Person A's schema and seed files are not yet available. -- The setup and seed commands must be verified with those files. -- Temporary development data must be replaced with the agreed sample data. -- Integration with the frontend and deployment on the VM remain to be verified. - -## AI usage - -ChatGPT was used for step-by-step guidance and code suggestions for backend configuration, database access, GraphQL operations, API tests, and documentation. - -Verification performed on the resulting implementation is recorded above. Integration checks listed as remaining have not yet been completed. diff --git a/docs/database.md b/docs/database.md deleted file mode 100644 index 7e01ae0..0000000 --- a/docs/database.md +++ /dev/null @@ -1,82 +0,0 @@ -# Database – Spillkveld - -## Overview - -The project uses PostgreSQL to store board game data. The database schema and seed data are defined in two SQL files: - -- `server/db/schema.sql` – creates the `games` table and its constraints. -- `server/db/seed.sql` – inserts the 12 example games from `src/data/games.ts`. - -The original game IDs and fields are preserved, including `icon` and `tone`. - -## Local setup - -PostgreSQL 17 was installed and tested locally on macOS using Homebrew. - -Install PostgreSQL: - -```bash -brew install postgresql@17 -``` - -Start the database service: - -```bash -brew services start postgresql@17 -``` - -Create a local database: - -```bash -/opt/homebrew/opt/postgresql@17/bin/createdb spillkveld -``` - -Run the following commands from the project root: - -```bash -/opt/homebrew/opt/postgresql@17/bin/psql -X -v ON_ERROR_STOP=1 -d spillkveld -f server/db/schema.sql -/opt/homebrew/opt/postgresql@17/bin/psql -X -v ON_ERROR_STOP=1 -d spillkveld -f server/db/seed.sql -``` - -The PostgreSQL executable path shown above is specific to this local Homebrew installation. On other systems, the commands may use `psql` directly. - -## Database structure - -The `games` table contains the following fields: - -| Column | Type | Constraint | -|---|---|---| -| id | TEXT | PRIMARY KEY | -| name | TEXT | NOT NULL | -| description | TEXT | NOT NULL | -| min_players | INTEGER | NOT NULL, >= 1 | -| max_players | INTEGER | NOT NULL, >= min_players | -| minutes | INTEGER | NOT NULL, > 0 | -| category | TEXT | NOT NULL | -| icon | TEXT | NOT NULL | -| tone | TEXT | NOT NULL | - -## Seed data - -The seed file contains 12 example games based on the existing prototype data in `src/data/games.ts`. - -It uses `ON CONFLICT (id) DO NOTHING` to prevent duplicate rows when executed multiple times. Existing rows are left unchanged. - -## Local testing - -The following tests were completed using PostgreSQL 17.11: - -1. **Schema creation:** Executing `schema.sql` returned `CREATE TABLE`. -2. **Seed insertion:** Executing `seed.sql` returned `INSERT 0 12`. -3. **Repeated seed execution:** Running `seed.sql` again returned `INSERT 0 0`, confirming that no duplicate rows were inserted. -4. **Row count:** `SELECT COUNT(*) FROM games;` returned 12. -5. **Constraint validation:** An attempt to insert a game with `min_players = 0` was rejected by the `games_min_players_check` constraint. -6. **Player range validation:** An attempt to insert a game with `min_players = 5` and `max_players = 2` was rejected by the `games_check` constraint, confirming that `max_players` cannot be lower than `min_players`. -7. **Game duration validation:** An attempt to insert a game with `minutes = 0` was rejected by the `games_minutes_check` constraint. -8. **NOT NULL validation:** An attempt to insert a game with `name = NULL` was rejected, confirming that the name field is required. - -The database schema and seed data were tested locally. Backend integration and deployment are handled separately. - -## AI usage - -ChatGPT was used to explain PostgreSQL setup, assist with drafting the SQL schema and seed file, and provide testing instructions. The SQL files were executed and their results verified locally. \ No newline at end of file diff --git a/docs/deployment.md b/docs/deployment.md deleted file mode 100644 index b782104..0000000 --- a/docs/deployment.md +++ /dev/null @@ -1,172 +0,0 @@ -# Drift og publisering - -## Status - -VM: `it2810-32.idi.ntnu.no`. SSH: `ssh bjoabe@it2810-32.idi.ntnu.no`. -Bruk NTNU-nettet eller VPN utenfor campus. - -Kontrollert og installert på VM: - -- Apache kjører og er aktivert for automatisk oppstart. -- Prosjekt 1 ligger i `/var/www/html/project1` og svarer med HTTP 200. -- Node.js 22.22.1 og npm 9.2.0 er installert. -- PostgreSQL 18.6 kjører på `127.0.0.1:5432`. - -Databasen `spillkveld` og brukeren `spillkveld_app` er opprettet. Skriptets -innloggingstest mot databasen passerer. Skjema og spilldata er ikke lagt inn. -Apache-konfigurasjon og tjenestefiler er installert og kontrollert på VM. -Testtjenesten kjører på `127.0.0.1:3001` og er aktivert for automatisk oppstart. -`/project2/` viser den midlertidige testsiden med HTTP 200, og Apache videresender -`/project2/graphql` til testserveren med forventet HTTP 503 og `setupOnly: true`. -Prosjekt 1 svarer fortsatt med HTTP 200. Den ekte backenden og frontend er -ikke publisert, og automatisk oppstart etter en faktisk VM-omstart er ikke testet. - -## Database - -Kjør `deploy/setup-database.sh` på VM med administratorrettigheter: - -```bash -sudo bash setup-database.sh -``` - -Skriptet oppretter databasen `spillkveld` og innloggingsbrukeren -`spillkveld_app`. Brukeren eier denne databasen, men får ikke rettigheter -til å administrere PostgreSQL, opprette databaser eller opprette andre brukere. - -Et tilfeldig passord genereres på VM. `DATABASE_URL`, `PORT=3001` og -`HOST=127.0.0.1` lagres i `/etc/spillkveld/backend.env`, som bare root kan -lese. Den kommende backend-tjenesten skal lese denne filen gjennom systemd. -Passord og den faktiske miljøfilen skal ikke kopieres inn i repoet. - -Skriptet kontrollerer innlogging med applikasjonsbrukeren. Det kan kjøres -på nytt med samme miljøfil uten å bytte passord eller tømme databasen. -Hvis det finnes et annet oppsett med samme navn, stopper det for kontroll. - -Skjema og eksempeldata lastes senere fra `server/db/schema.sql` og -`server/db/seed.sql`, med samme databasebruker som backend. - -## Apache og tjenester - -Last opp hele `deploy/`-mappen og kjør fra mappen over den på VM: - -```bash -sudo bash deploy/install-runtime.sh -``` - -Oppsettet oppretter systembrukeren `spillkveld` uten interaktiv innlogging og -installerer følgende filer: - -- `/etc/apache2/conf-available/spillkveld.conf` -- `/etc/systemd/system/spillkveld-backend.service` -- `/etc/systemd/system/spillkveld-setup-test.service` - -Backend-tjenesten leser `/etc/spillkveld/backend.env` og kjører `npm start` -fra `/opt/spillkveld/server`. Den aktiveres for automatisk oppstart når den -ekte backenden er lagt inn og bygget. Inntil da er bare testtjenesten aktivert. - -Foreløpig kjører en separat testserver på `127.0.0.1:3001`. Den gir -`setup-only` fra `/health` og HTTP 503 med `setupOnly: true` fra `/graphql`. -Dette kontrollerer prosessdrift og Apache-proxyen, ikke database–GraphQL-flyten. -Testsiden under `/project2/` er tydelig merket som midlertidig. - -Skriptet kontrollerer Apache-konfigurasjonen før reload, og bekrefter at -prosjekt 1 og testsiden svarer med HTTP 200. Det erstatter ikke en allerede -publisert frontend eller en kjørende ekte backend. - -## Når backend og databasefiler er klare - -1. Legg backend-koden i `/opt/spillkveld/server`. Installer og bygg med de - dokumenterte npm-kommandoene. Systembrukeren `spillkveld` må kunne lese - programfilene. Backend skal bruke `DATABASE_URL`, `HOST` og `PORT` fra miljøet. -2. Last databaseskjema og eksempeldata gjennom applikasjonsbrukerens tilkobling. - Kjør skjema før seed. Ikke bruk PostgreSQL-administrator som backend-bruker. -3. Stopp testserveren før den ekte backenden startes: - - ```bash - sudo systemctl disable --now spillkveld-setup-test.service - sudo systemctl enable --now spillkveld-backend.service - curl --fail http://127.0.0.1:3001/health - ``` - -4. Bygg og publiser frontend med verdiene nedenfor. Kontroller den første ekte - flyten: databasespørring → GraphQL → synlig spill i klienten. - -## Adresser og produksjonsbygg - -- Frontend: `/var/www/html/project2`, tilgjengelig under `/project2/`. -- Backend: `127.0.0.1:3001`, API under `/graphql`, kjørt som systemd-tjeneste. -- Apache skal videresende `/project2/graphql` til backend. -- Frontend bygges med Vite-base `/project2/` og - `VITE_GRAPHQL_URL=/project2/graphql`. -- Prosjekt 1 skal fortsette å fungere under `/project1/`. - -## Ende-til-ende-test - -Playwright-oppsettet er i `playwright.config.ts`. Testen i -`e2e/catalog.e2e.ts` søker etter Azul og åpner detaljer gjennom ekte GraphQL. -Testen bruker ikke mocker. Backend og database må derfor være klare før den -kan passere. Filendelsen `.e2e.ts` skiller den fra Vitest-testene. - -Installer først prosjektets avhengigheter med `npm ci`, deretter nettleseren: - -```bash -npx playwright install chromium -``` - -Mot en lokal klient og backend som allerede kjører: - -```bash -npm run test:e2e -``` - -Mot VM fra PowerShell på NTNU-nettet/VPN: - -```powershell -$env:PLAYWRIGHT_BASE_URL = 'http://it2810-32.idi.ntnu.no/project2/' -npm run test:e2e -``` - -`npm run test:e2e -- --list` kontrollerer at testen kan oppdages, men er ikke -en gjennomført integrasjonstest. Samlet README ferdigstilles når dokumentasjonen -for alle delene er tilgjengelig. - -## Kontroll og feilsøking - -```bash -node --version -npm --version -pg_isready -h 127.0.0.1 -p 5432 -pg_lsclusters -systemctl status apache2 -systemctl status spillkveld-setup-test -curl -I http://localhost/project1/ -curl -I http://localhost/project2/ -curl http://127.0.0.1:3001/health -curl -X POST http://localhost/project2/graphql -``` - -Etter publisering av ekte backend brukes følgende for status, logger og omstart: - -```bash -systemctl status spillkveld-backend -sudo journalctl -u spillkveld-backend -n 50 --no-pager -sudo systemctl restart spillkveld-backend -``` - -Ved Apache-feil: kjør `sudo apache2ctl configtest` før reload og se -`sudo journalctl -u apache2 -n 50 --no-pager`. HTTP 503 med `setupOnly: true` -er forventet så lenge testserveren kjører. Etter bytte til ekte backend skal -`/health` i stedet bekrefte databasetilkoblingen etter API-avtalen. - -## KI-erklæring - -Codex er brukt til å gjennomgå tidligere VM-oppsett, lage database- og -driftsoppsett, forberede Playwright-testen og skrive dokumentasjonen. -Installasjon og status er kontrollert med versjonskommandoer, PostgreSQLs -tilkoblingskontroll og HTTP-kall til prosjekt 1. Databaseoppsettet er kjørt -og bekreftet med innlogging som `spillkveld_app` i databasen `spillkveld`. -Runtime-oppsettet er installert og kontrollert med syntakssjekk, tjenestestatus -og HTTP-kall gjennom Apache. De 13 eksisterende testene, linting, formateringssjekk -og produksjonsbygg passerer. Playwright finner testen med `--list`, men testen -mot ekte backend og database er ikke kjørt ennå. Backend-integrasjon og faktisk -VM-omstart må kontrolleres når resten av løsningen er klar. diff --git a/docs/frontend.md b/docs/frontend.md deleted file mode 100644 index 680682e..0000000 --- a/docs/frontend.md +++ /dev/null @@ -1,313 +0,0 @@ -# Frontend Documentation - -## 1. Introduction - -The frontend of *Spillkveld* is developed using React and TypeScript, with Vite as -the development and build tool. - -The application allows users to browse a catalog of board games, search for specific games, -apply filters, sort results, and view detailed information about individual games. -Users can also create a personal selection of games, which is preserved in the browser -using local storage. - -The frontend uses Apollo Client to manage GraphQL queries and communicate with the backend. -During frontend development, mocked GraphQL responses are used to test functionality -independently of the backend implementation. - -## 2. Technology Stack - -The frontend uses the following technologies: - -- **React:** Component-based user interface. -- **TypeScript:** Static typing for improved maintainability and reliability. -- **Vite:** Development server and production build tool. -- **Apollo Client:** GraphQL queries, caching, and client-side data management. -- **GraphQL:** API interface between the frontend and backend. -- **Vitest:** Unit and component testing. -- **React Testing Library:** Testing user interactions and rendered components. -- **Prettier:** Consistent code formatting. -- **ESLint:** Static code analysis and linting. -- **Playwright:** Dependency and test command prepared for end-to-end testing. - -## 3. Frontend Architecture - -The frontend is organized into separate modules for the user interface, GraphQL operations, -application data, and utility functions. - -### 3.1 Project Structure - -The main frontend files and directories are: - -- `src/App.tsx` – Main application component, including the game catalog, search, filtering, - sorting, pagination, game details, and game selection. -- `src/main.tsx` – Application entry point and Apollo Provider setup. -- `src/lib/apollo.ts` – Apollo Client configuration and GraphQL endpoint. -- `src/graphql/queries.ts` – GraphQL queries used by the frontend. -- `src/graphql/types.ts` – TypeScript definitions for GraphQL data and variables. -- `src/graphql/mocks.ts` – Mocked GraphQL responses used during frontend development and testing. -- `src/data/games.ts` – Sample board game data used by the mock implementation. -- `src/lib/catalog.ts` – Catalog filtering, sorting, and pagination logic for mocked data. -- `src/lib/selection.ts` – Local storage utilities for managing the user's selected games. -- `src/test/setup.ts` – Test environment configuration. - -### 3.2 Apollo Client and GraphQL - -Apollo Client is configured in `src/lib/apollo.ts` and provided to the React application -through `ApolloProvider` in `src/main.tsx`. - -The frontend uses GraphQL queries to retrieve game information instead of accessing - the sample data directly from the UI components. - -The following operations are defined: - -- `GET_GAMES` – Retrieves a paginated game catalog with search, filtering, and sorting parameters. -- `GET_GAME` – Retrieves detailed information about a specific game. -- `GET_CATEGORIES` – Retrieves the available game categories. -- `GET_GAMES_BY_IDS` – Retrieves games belonging to the user's selection. - -The GraphQL endpoint can be configured through the `VITE_GRAPHQL_URL` environment -variable. During local development, the default `/graphql` path is forwarded to the -backend through the Vite development proxy. - -### 3.3 Mocked Data - -The frontend can be tested independently of the backend by using Apollo's `MockLink`. - -Mocked GraphQL responses are generated from the sample games in `src/data/games.ts`. -The mock implementation supports the same queries and input parameters expected -by the frontend. - -This allows frontend functionality - -## 4. User Interface and Features - -### 4.1 Game Catalog - -The main page displays a catalog of board games. Each game is presented with relevant -information, allowing users to browse the available games and open a detailed view. - -The catalog data is retrieved through the `GET_GAMES` GraphQL query using Apollo Client. - -The interface includes loading, error, and empty-result states to provide feedback -when data is being fetched, a request fails, or no games match the selected criteria. - -### 4.2 Search and Filtering - -Users can search for board games by entering text in the search field. - -The search input uses a 300-millisecond debounce to avoid sending a new query for -every keystroke. - -The catalog also supports filtering based on player count, maximum playing time, -and game category. - -These parameters are passed to the `GET_GAMES` GraphQL query, allowing the catalog -results to reflect the user's selected criteria. - -### 4.3 Sorting and Pagination - -Users can sort the game catalog by name or playing time. - -The catalog uses pagination, displaying six games per page. Users can navigate -between pages to browse additional results. - -Search, filtering, sorting, and pagination are handled through GraphQL query variables. - -### 4.4 Game Details - -Users can open a game to view additional information, including its description, -player count, playing time, and category. - -The detailed information is retrieved through the `GET_GAME` GraphQL query. - -The interface also handles loading and error states when retrieving game details. - -### 4.5 Game Selection - -Users can add games to a personal selection and remove games from it. - -The selection is stored in the browser's `localStorage`, allowing it to persist -across page reloads. - -The application stores the selected game IDs rather than complete game objects. -The corresponding game information is retrieved using the `GET_GAMES_BY_IDS` GraphQL query. - -This separates the user's locally stored selection from the game data provided -through GraphQL. - -## 5. Testing and Code Quality - -### 5.1 Unit and Component Testing - -The frontend uses Vitest and React Testing Library for automated testing. - -The tests cover both application logic and user interactions, including: - -- Searching for games and opening game details. -- Displaying an empty state when no games match the search. -- Adding and removing games from the selection. -- Persisting selected games using local storage. -- Navigating between catalog pages. -- Retrieving game details and categories through GraphQL. -- Handling GraphQL request failures and retrying failed requests. -- Catalog filtering, sorting, and pagination logic. - -Apollo's `MockLink` is used to provide controlled GraphQL responses during component - tests, allowing the frontend to be tested independently of a running backend. - -The test files include: - -- `src/App.test.tsx` -- `src/graphql/queries.test.tsx` -- `src/lib/catalog.test.ts` - -The current frontend test suite contains 11 tests. - -Run the tests using: - -```bash -npm test -``` - -### 5.2 Linting - -ESLint is used to identify potential code quality issues and enforce consistent -coding practices. - -Run ESLint using: - -```bash -npm run lint -``` - -### 5.3 Code Formatting - -Prettier is configured using `.prettierrc.json`, with excluded directories defined -in `.prettierignore`. - -To automatically format the frontend source files and Vite configuration, run: - -```bash -npm run format -``` - -To check formatting without modifying files, run: - -```bash -npm run format:check -``` - -### 5.4 Production Build - -The frontend is built using TypeScript and Vite. - -To verify TypeScript compilation and generate the production build, run: - -```bash -npm run build -``` - -### 5.5 End-to-End Testing - -Playwright has been added as a development dependency, and the following command -is available: - -```bash -npm run test:e2e -``` - -The implementation and configuration of Playwright end-to-end tests are handled -separately as part of the team's testing responsibilities. - -The presence of the command does not mean that the end-to-end tests have already -been implemented or executed successfully. - -## 6. Development and Configuration - -### 6.1 Local Development - -The frontend uses Vite as its development server. - -After cloning the repository, install the project dependencies: - -```bash -npm install -``` - -Start the development server: - -```bash -npm run dev -``` - -The development server runs on: - -`http://localhost:5173` - -The Vite configuration uses port `5173` with `strictPort` enabled, meaning the server - will not automatically switch to another port if it is occupied. - -### 6.2 GraphQL Endpoint - -Apollo Client is configured in `src/lib/apollo.ts`. - -By default, the frontend sends GraphQL requests to `/graphql`. During local development, -Vite proxies these requests to: - -`http://127.0.0.1:3001` - -This allows the frontend and backend to run on separate local ports without requiring -the frontend to use the backend address directly. - -The GraphQL endpoint can be overridden using the `VITE_GRAPHQL_URL` environment variable. - -An `.env.example` file is provided to document this configuration. - -### 6.3 Production Build and Base Path - -The Vite configuration uses the following base path: - -`/project2/` - -This allows the frontend to be deployed under a subdirectory instead of the root -of a web server. - -The production build is generated using: - -```bash -npm run build -``` - -The generated files are placed in the `dist` directory. - -For deployment under `/project2/`, the GraphQL endpoint must be configured to match -the backend routing. For example, if the backend is exposed at `/project2/graphql`, -klset `VITE_GRAPHQL_URL=/project2/graphql` before building the frontend. - -The final deployment configuration and backend routing must be coordinated with the -team member responsible for deployment. - -### 6.4 Frontend and Backend Integration - -The frontend defines GraphQL queries and TypeScript types for the expected API responses. - -During frontend development, mocked GraphQL responses allow the interface and tests -to work independently of the backend. - -Integration with the running backend requires the backend schema to support the -expected queries, input parameters, and response fields. - -Once the backend is available, the frontend should be tested against real GraphQL -responses to verify that the agreed API contract is implemented correctly. - -## AI Usage Declaration - -ChatGPT was used as a supporting tool during frontend development, mainly for -discussing implementation approaches, troubleshooting, reviewing code, and -suggesting improvements. - -AI was also used to help with React, TypeScript, Apollo Client, GraphQL -integration, automated tests, and documentation. The suggested solutions were -reviewed, adjusted where necessary, and tested to ensure they worked as -intended. All changes were verified using automated tests, ESLint, and -production builds. \ No newline at end of file diff --git a/server/scripts/run-sql.mjs b/server/scripts/run-sql.mjs index 2da3196..72eb765 100644 --- a/server/scripts/run-sql.mjs +++ b/server/scripts/run-sql.mjs @@ -1,7 +1,7 @@ import 'dotenv/config' -import { existsSync } from 'node:fs' +import { existsSync, readFileSync } from 'node:fs' import { fileURLToPath, URL } from 'node:url' -import { spawnSync } from 'node:child_process' +import pg from 'pg' const files = { setup: new URL('../db/schema.sql', import.meta.url), @@ -28,23 +28,20 @@ if (!existsSync(filePath)) { process.exit(1) } -const result = spawnSync( - 'psql', - ['-X', '-v', 'ON_ERROR_STOP=1', '-f', filePath], - { - stdio: 'inherit', - env: { - ...process.env, - PGDATABASE: process.env.DATABASE_URL, - }, - }, -) - -if (result.error) { +const client = new pg.Client({ + connectionString: process.env.DATABASE_URL, + connectionTimeoutMillis: 5000, +}) + +try { + await client.connect() + await client.query(readFileSync(filePath, 'utf8')) + console.log(`Database ${operation} completed.`) +} catch { console.error( - 'Could not start psql. Check that PostgreSQL tools are on PATH.', + `Database ${operation} failed. Check the connection and SQL file.`, ) - process.exit(1) + process.exitCode = 1 +} finally { + await client.end() } - -process.exit(result.status ?? 1)