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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .env.example
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,11 @@
# ssh -N -L 5433:/var/run/postgresql/.s.PGSQL.5432 <brukernavn>@it2810-34.idi.ntnu.no
DATABASE_URL=postgres://gruppe@localhost:5433/norwegian_explorer

# På VM-en (koble til via socket i stedet for nettverk):
# På VM-en (koble til via socket i stedet for nettverk). Backend skal lytte på port 3001,
# og NODE_ENV=production skrur av GraphiQL:
# DATABASE_URL=postgres://gruppe@%2Fvar%2Frun%2Fpostgresql/norwegian_explorer
# PORT=3001
# NODE_ENV=production

# Lokal testing av passordtilbakestilling: lagrer e-post som .eml, sender ingenting.
# APP_PUBLIC_URL=http://localhost:5173/
Expand Down
1 change: 1 addition & 0 deletions .nvmrc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
24
5 changes: 5 additions & 0 deletions .prettierignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
dist
node_modules
**/node_modules.nosync
package-lock.json
server/data.nosync
5 changes: 5 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"semi": false,
"singleQuote": true,
"printWidth": 100
}
101 changes: 101 additions & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
# Retningslinjer for KI-assistenter

Denne fila leses av KI-verktøy (Claude Code, Copilot, Codex m.fl.) som jobber i repoet. Den gjelder også for oss som skriver kode selv. Mennesket som ber om endringen er ansvarlig for alt som merges.

## Prosjektet

Norwegian Explorer: søkbar katalog over turer i Norge. React + TypeScript + Vite i frontend, GraphQL (Yoga) i Node/TypeScript i backend, PostgreSQL/PostGIS på VM-en. Oppgaven er et studieprosjekt i IT2810, og koden skal kunne leses av eksterne.

Les [README.md](README.md) og [docs/arkitektur.md](docs/arkitektur.md) før du endrer noe større.

## Struktur

```
src/pages/ Sider (Home, Profile, Fjellomrade)
src/features/ Større deler (Explore, RouteBuilder, ElevationProfile)
src/components/ Delte komponenter
src/lib/ Hjelpefiler uten visning (graphql, format, gpx …)
server/graphql/ Skjemaet delt etter tema, én modul per område
server/services/ Spørringer og beregninger resolverne bruker
server/lib/ Innlogging, grenser, e-post
server/db/ Tilkobling og oppsett av appens tabeller
docs/ Dokumentasjon
```

Legg ny kode i riktig mappe. Lag ikke nye filer i roten av `src/` eller `server/`.

## Kommandoer

Alt under skal gå uten feil før du sier at noe er ferdig:

```bash
npm run format:check # Prettier
npm run lint # ESLint
npx tsc -b # typesjekk
npm run build # bygg
```

Kjør `npm run format` for å rette formatering. Start lokalt med `npm run dev:server` og `npm run dev`.

## Kodekvalitet

- **Gjenbruk før du skriver nytt.** Søk i `src/lib/`, `server/lib/` og nabofilene først. Sjekk spesielt:
- `graphql()` i `src/lib/graphql.ts` for alle kall mot backend. Bruk aldri `fetch` direkte.
- `checkSession()` og `TOKEN_KEY` for innlogging. Ikke skriv tokennøkkelen som tekst.
- `formatKm()` i `src/lib/format.ts` og `lengthText()` for lengder.
- `authed()` i `server/lib/auth.ts` for resolvere som krever innlogging, og `setPassword()` for passordbytte.
- **Ikke kopier kode.** Står det samme i to filer, flytt det til én felles funksjon.
- **Del opp filer der det er naturlig.** Én fil skal ha ett tydelig ansvar. Trenger du å bla lenge for å finne ting, eller blander filen flere ansvar (for eksempel datahenting, kart og visning), er det tid for å dele den. Del etter ansvar og ikke etter linjeantall, og ikke del opp bare for å komme under en grense.
- **TypeScript overalt.** Ingen `.js` eller `.mjs`. Unngå `any`. Trengs det, skriv en kort kommentar om hvorfor.
- **Alle kall mot serveren skal ha timeout** og vise en forståelig feil til brukeren.
- **Ingen ubrukt kode.** Fjern døde imports, filer og kommentert-ut kode. Ikke legg igjen malfiler.
- **Søk, filtrering og sortering skjer på serveren** i SQL, aldri bare på det klienten har lastet.
- **Kommentarer på norsk**, som forklarer hvorfor, ikke hva. Koden skal kunne leses av eksterne.

## Tilgjengelighet og bærekraft

- Alle interaktive elementer skal kunne brukes med tastatur og ha tydelig fokus og navn (`aria-label`, `<label>`).
- Bruk semantisk HTML først, ARIA bare når det trengs. Statusmeldinger bruker `role="status"` eller `role="alert"`.
- Ikke bruk farge alene for å vise noe. Hold god kontrast.
- Kartet skal alltid ha en listevisning som alternativ.
- Last ikke mer data enn nødvendig: paginer, hent bare synlig kartutsnitt, og legg ikke til tunge biblioteker uten å vurdere størrelsen.

## Dokumentasjon underveis

Dokumentasjon er en del av endringen, ikke noe som gjøres etterpå.

- Endrer du struktur, kommandoer, miljøvariabler eller hvor noe hentes fra: oppdater `README.md`, `server/README.md` og `docs/arkitektur.md` i samme commit.
- Flytter eller omdøper du filer: søk etter gamle filnavn i alle `.md`-filer og rett dem.
- Legger du til en spørring, mutasjon eller tabell: beskriv den i `server/README.md`.
- Nye teknologivalg: skriv kort hvorfor du valgte det, ikke bare hva.
- Skriv på norsk i klar tekst. Ikke påstå at noe er testet hvis det ikke er det.

## Bruk av KI skal dokumenteres

Oppgaven krever at vi viser hvordan KI brukes og at vi har egen kontroll.

- Legg til en rad i [docs/bruk-av-ki.md](docs/bruk-av-ki.md) for hver større endring KI har gjort.
- Skriv i PR-beskrivelsen hva KI gjorde, hva du selv leste og endret, og hva du testet manuelt.
- Hold KI-endringer små og samlet om ett tema, så de kan leses gjennom.

## Git

- **Commit-meldinger:** Conventional Commits med norsk tekst: `feat:`, `fix:`, `refactor:`, `style:`, `chore:`, `docs:`, `test:`. Eksempel: `fix: sett timeout på kall i profilen`.
- Én commit per tema. Formatering og refaktorering skal ikke blandes med funksjonelle endringer.
- Ikke push til `main`. Lag en gren og en pull request. Alle PR-er må godkjennes av noen i gruppa som ikke selv har pushet koden (se `.github/CODEOWNERS`).
- Ikke commit `.env`, `node_modules`, `dist` eller andre filer som er ignorert.
- Ikke commit før du er bedt om det.

## Forsiktig med

- **Den delte databasen.** Ikke opprett brukere, endre tabeller eller kjør `ALTER`/`DROP` mot VM-databasen for å teste. Lag test-data lokalt, eller spør først.
- **Hemmeligheter.** Ikke skriv passord, tokens eller `DATABASE_URL` med passord inn i kode, dokumentasjon eller commit-meldinger.
- **Sikkerhet.** All SQL bruker parameteriserte spørringer (`$1`, `$2`). Sett aldri brukerinput rett inn i en SQL-streng. Valider input i resolverne.
- **Utenfor oppgaven.** Gjør bare det som er bedt om. Ser du noe annet som bør rettes, skriv det som et forslag i stedet for å endre det.

## Før du er ferdig

1. Alle fire kommandoene over går gjennom.
2. Dokumentasjonen er oppdatert.
3. Du har sagt ærlig hva som er testet og hva som ikke er det.
4. Det er ingen ubrukte filer eller duplisert kode igjen.
3 changes: 3 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Alle retningslinjer for dette repoet står i AGENTS.md. Følg dem.

@AGENTS.md
83 changes: 72 additions & 11 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,52 @@

Norwegian Explorer viser registrerte fotruter i Norge med søk, kart og rutedetaljer. Frontend er laget med React, TypeScript og Vite. Backend bruker GraphQL, Node.js og PostgreSQL/PostGIS.

## Kom i gang

Slik installerer og kjører du appen lokalt. Backend henter alle data fra databasen på gruppas VM, så du trenger tilgang til VM-en (NTNU-nettet eller VPN).

**Forutsetninger**

- **Node 24** (22.18 eller nyere fungerer). Backend kjører `.ts`-filene direkte, så den trenger ikke bygges. Sjekk med `node -v`, og bruk gjerne `nvm install` i prosjektmappen, som leser `.nvmrc`.
- En SSH-bruker på VM-en (`it2810-34.idi.ntnu.no`), siden databasen bare nås derfra.

**Steg**

1. Installer avhengighetene:

```bash
npm ci
```

2. Lag `.env` fra malen. Den peker på databasen gjennom en SSH-tunnel, og trenger normalt ingen endringer:

```bash
cp .env.example .env
```

3. Åpne tunnelen til databasen i en egen terminal, og la den stå åpen (bytt `brukernavn`):

```bash
ssh -N -L 5433:/var/run/postgresql/.s.PGSQL.5432 brukernavn@it2810-34.idi.ntnu.no
```

4. Start backend i en ny terminal. Den lytter på `127.0.0.1:4000`, og GraphiQL ligger på <http://127.0.0.1:4000/graphql>:

```bash
npm run dev:server
```

5. Start frontend i en tredje terminal, og åpne <http://localhost:5173>. Vite sender `/graphql` videre til backend:

```bash
npm run dev
```

Slik publiseres appen på VM-en: se [Publisering på VM-en](#publisering-på-vm-en). Hvordan backend er bygget opp, står i [server/README.md](server/README.md), og oppsettet på VM-en i [docs/oppsett-vm.md](docs/oppsett-vm.md). Den publiserte versjonen ligger på <http://it2810-34.idi.ntnu.no/project2/>.

## Data og API

Kartverkets Tur- og friluftsruter, kommune- og fylkesgrenser og OpenStreetMap-uttrekket er importert i PostgreSQL-databasen `norwegian_explorer` på VM-en. Den bearbeidede rutekatalogen og områdekoblingene er også importert. Frontend henter data gjennom GraphQL-endepunktet `/graphql` i `server/index.mjs`.
Kartverkets Tur- og friluftsruter, kommune- og fylkesgrenser og OpenStreetMap-uttrekket er importert i PostgreSQL-databasen `norwegian_explorer` på VM-en. Den bearbeidede rutekatalogen og områdekoblingene er også importert. Frontend henter data gjennom GraphQL-endepunktet `/graphql` (se `server/graphql/`).

Gruppemedlemmer som er logget inn på VM-en via SSH, kan åpne databasen med:

Expand All @@ -31,20 +74,25 @@ Det finnes ingen offisiell avgrensning av landskapsområder som «Jotunheimen»,

Mange hytter ligger like utenfor vernegrensene, så grensene fra OSM og Miljødirektoratet er utvidet med 3 km. De er også forenklet til omtrent 100 m nøyaktighet, så SQL-fila holder seg liten, og hver rad har en kommentar om hvor grensen kommer fra. Kolonnen `ssr_id` peker til Kartverkets offisielle navn i stedsnavnregisteret (SSR) der området er registrert. Grensen ligger i én kolonne av typen `MultiPolygon`, og både opptellingen og turpunktene på kartet hentes med `ST_Intersects` mot den.

## Start databaseversjonen lokalt

PostgreSQL må kjøre, og databasen `norwegian_explorer` må inneholde det importerte Kartverket-skjemaet. Kjør deretter i to terminaler:
## Mappestruktur

```bash
npm run dev:server
```

```bash
npm run dev
src/
pages/ Sidene: Home, Profile og Fjellomrade (egen inngang i fjellomrade.html)
features/ Større deler av appen: Explore (søk og kart), RouteBuilder, ElevationProfile
components/ Delte komponenter, for eksempel SiteNav
lib/ Hjelpefiler uten visning: GraphQL-klient, rutetyper og søkespørringer, formatering, GPX,
hooks (useDebouncedValue, useAuthenticated)
server/
graphql/ Skjemaet delt etter tema: routes, auth, profile, builder, mountains
services/ Spørringer og beregninger som resolverne bruker
lib/ Innlogging, grenser for forespørsler og e-post
db/ Databasetilkobling og oppsett av appens tabeller
sql/ SQL-filer som kjøres for hånd i DBeaver
deploy/ Eksempler på Apache- og systemd-konfigurasjon for VM-en
docs/ Dokumentasjon
```

Vite sender `/graphql` videre til backend på `127.0.0.1:4000`.

## Profiler

Ved oppstart oppretter backend skjemaet `norwegian_explorer_app` med tabeller for brukere, sesjoner og favoritter. Brukeren kan:
Expand All @@ -58,6 +106,19 @@ Passord hashes med Node.js `scrypt` og en tilfeldig salt. Sesjonstoken lagres so

Favoritter kobles til den stabile rute-ID-en, slik at like rutenavn eller rutenumre ikke blandes sammen.

## Publisering på VM-en

Appen ligger på `http://it2810-34.idi.ntnu.no/project2/`. Frontend bygges med `npm run build` (som setter riktig adresse, `/project2/`) og kopieres til Apache. Backend kjører som en tjeneste på VM-en på port 3001, og Apache sender `/project2/graphql` videre til den. Konfigurasjonsfilene ligger i [deploy/](deploy/), og steg for steg står i [docs/oppsett-vm.md](docs/oppsett-vm.md).

Du kan prøve produksjonsbygget lokalt, med kallene sendt til backend på samme måte som Apache gjør:

```bash
npm run build
npm run preview
```

Åpne <http://localhost:4173/project2/> mens backend kjører (`npm run dev:server`).

## Bygg og kontroll

```bash
Expand Down
12 changes: 12 additions & 0 deletions deploy/apache-project2.conf.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
# Sender kall fra appen til backend, som kjører på VM-en på port 3001.
# Kopier denne filen til /etc/apache2/conf-available/project2.conf på VM-en, og kjør:
# sudo a2enmod proxy proxy_http
# sudo a2enconf project2
# sudo systemctl reload apache2
# Appen (de bygde filene) ligger i /var/www/html/project2/ og trenger ingen egen konfigurasjon.

# Noen kall (rutebygger, høydeprofil) kan ta tid, så vi venter lenger enn standard
ProxyTimeout 90

ProxyPass "/project2/graphql" "http://127.0.0.1:3001/graphql"
ProxyPassReverse "/project2/graphql" "http://127.0.0.1:3001/graphql"
23 changes: 23 additions & 0 deletions deploy/project2-backend.service.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Holder backend i gang på VM-en og starter den på nytt hvis den stopper eller VM-en startes på nytt.
# Bytt ut BRUKERNAVN med brukernavnet ditt på VM-en.
# Bytt også ut stien til node (ExecStart). Sjekk den med: which node
# Er node installert med nvm, ligger den under ~/.nvm/versions/node/<versjon>/bin/node. Systemd leser
# ikke .bashrc, så stien må skrives helt ut.
# Kopier denne filen til /etc/systemd/system/project2-backend.service, og kjør:
# sudo systemctl daemon-reload
# sudo systemctl enable --now project2-backend
# Se loggen med: journalctl -u project2-backend -f
[Unit]
Description=Norwegian Explorer backend (project2)
After=network.target postgresql.service

[Service]
Type=simple
User=BRUKERNAVN
WorkingDirectory=/home/BRUKERNAVN/project2
ExecStart=/home/BRUKERNAVN/.nvm/versions/node/NODE-VERSJON/bin/node --env-file=.env server/index.ts
Restart=on-failure
RestartSec=5

[Install]
WantedBy=multi-user.target
72 changes: 72 additions & 0 deletions docs/arkitektur.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
# Arkitektur

Denne fila forklarer hvordan appen henger sammen, og hvor dataene kommer fra. Mappene er beskrevet i [README.md](../README.md#mappestruktur), og databasen og fjelldelen i [server/README.md](../server/README.md).

## Oversikt

```
Nettleser (React + Vite) ── /graphql ──> Node-server (GraphQL Yoga) ──> PostgreSQL + PostGIS
src/ server/ på VM-en
```

All kommunikasjon mellom klient og server går gjennom ett GraphQL-endepunkt, `/graphql`. Under utvikling sender Vite kallene videre til serveren på `127.0.0.1:4000` (se `vite.config.ts`).

## Frontend

- **Sider** ligger i `src/pages/`. `App.tsx` velger hvilken som vises, og navigerer med URL-fragmentet (`#explore`, `#profile`, `#reset-password/…`) i stedet for et eget ruterbibliotek.
- **Tilstand** holdes med Reacts egne hooks. `App.tsx` eier det som deles mellom sidene: innlogging og valgt tur. Resten ligger lokalt i komponentene.
- **Henting av data** går gjennom én funksjon, `graphql()` i `src/lib/graphql.ts`. Den sender spørringen, setter timeout og gjør feil om til lesbare meldinger. Ingen annen kode bruker `fetch` mot serveren.
- **Innlogging:** tokenet fra serveren lagres i `localStorage` under `TOKEN_KEY`. `checkSession()` i `src/lib/graphql.ts` sjekker om det fortsatt gjelder, og fjerner det hvis ikke. `App.tsx`, `lib/useAuthenticated.ts` (som siden med fjellområder bruker) og `AuthGate.tsx` bruker den samme funksjonen.
- **Søk:** forsiden og Utforsk bruker samme søkespørring og typer fra `src/lib/routeSearch.ts` og `src/lib/routeTypes.ts`. `useDebouncedValue` i `src/lib/` venter på at brukeren slutter å skrive, før et søk sendes.
- **Sider med mye innhold er delt i mapper:** `pages/Profile/` har en fil per fane (`OverviewTab`, `SettingsTab`, `TripsTab`), `AuthGate` og `profileApi.ts` med alle kall mot profilen. `pages/Fjellomrade/` har oversikten, områdevisningen, kartet og `data.ts`. `features/RouteBuilder/` har kartet (`useBuilderMap`), søket, rundturforslag, punkter og `builderApi.ts`.
- **Formatering** av tall og lengder ligger i `src/lib/format.ts` (`formatKm`) og `lengthText` i `src/lib/routeTypes.ts`, så alle sider viser lengder likt.

### Utforsk (`src/features/Explore/`)

Utforsk er hovedsiden: søk, filtre, liste og kart.

- Søkefeltet venter litt etter siste tastetrykk før det spør serveren (debounce), så det ikke sendes et kall per tegn.
- Kartet (MapLibre) melder fra om synlig utsnitt etter en kort pause, og bare turer i utsnittet hentes.
- Listen er delt i sider på 12 (`PAGE_SIZE`). Klienten sender `offset` og `limit`.
- **Søk, filtrering og sortering skjer på serveren**, i SQL, og gjelder hele resultatsettet, ikke bare det som er lastet inn.

## Backend

`server/index.ts` setter sammen skjemaet fra modulene i `server/graphql/`:

| Modul | Ansvar |
| ---------------------- | ----------------------------------------------------------------- |
| `graphql/routes.ts` | Tursøk, rutedetaljer, populære turer, kommuner, OSM i kartutsnitt |
| `graphql/auth.ts` | Registrering, innlogging, passord |
| `graphql/profile.ts` | Favoritter, turstatus, dagbok, månedsmål, «Mitt sted» |
| `graphql/builder.ts` | Egne ruter, rundturforslag, høydeprofil, stopp langs turen |
| `graphql/mountains.ts` | Fjellområder og turpunkter |

**Innlogging og sikkerhet**

- Passord lagres som scrypt-hash med eget salt, aldri i klartekst.
- Innloggingen gir et tilfeldig token. Databasen lagrer bare en hash av tokenet, og sesjonen varer 30 dager.
- Resolvere som krever innlogging er pakket i `authed()` (`server/lib/auth.ts`). Den sjekker tokenet før noe annet skjer.
- `server/lib/authLimits.ts` begrenser antall forsøk, og `server/lib/requestSafety.ts` setter grenser for størrelsen på spørringer.

**Mellomlagring:** fjellområdene huskes i 10 minutter og høydeprofiler i 1 time, fordi dataene sjelden endres og beregningen er dyr.

## Database

Alt ligger i databasen `norwegian_explorer` på VM-en, delt i skjemaer:

| Skjema | Innhold |
| ------------------------ | ---------------------------------------------------------------------------------------- |
| `turogfriluftsruter_…` | Kartverkets rådata (endres ikke) |
| `norwegian_explorer_app` | Appens egne tabeller: brukere, sesjoner, favoritter, turer, mål, egne ruter, rutekatalog |
| `silver` | Rensede data for fjelldelen |
| `osm_import` | OpenStreetMap-uttrekket |

Brukergenererte data (profiler, favoritter, turstatus, dagbok, mål og egne ruter) lagres i `norwegian_explorer_app` og vises igjen når brukeren logger inn.

## Slik legger du til noe nytt

- **Ny spørring eller mutasjon:** legg typene og resolveren i riktig modul i `server/graphql/`. Krever den innlogging, pakk den i `authed()`. Kall den fra klienten med `graphql()`.
- **Ny endring på databasen som må være atomisk:** bruk `withTransaction()` i `server/db/db.ts`, som committer eller ruller tilbake og alltid gir tilkoblingen tilbake. Delte kontroller av input (rute-id, passord, kartutsnitt) ligger i `server/lib/validation.ts`.
- **Ny side:** lag en mappe i `src/pages/`, og vis den fra `App.tsx`.
- **Ny delt hjelpefunksjon:** legg den i `src/lib/` (frontend) eller `server/lib/` (backend), og sjekk først om den finnes.
Loading