# Lektion 17 — Øvelser

Man lærer et repository bedst ved at flytte sin egen route over på det og
se listen huske. Detaljerne, der ikke står på slidesne, står her.

Hvis du ikke nåede [forberedelsen](forberedelse.md), så kør `hent.js` mod din
egen database først. Det tager 15 minutter, og så ved du, at forbindelsen
virker, før du rører `server.js`.

| Øvelse | Hvornår | Tid |
|---|---|---|
| **1** Listen husker | Efter quizzen om repository og `GET` | ~35 min |
| **2** `POST` og testskemaet | Efter quizzen om `POST` | ~30 min |
| **3** Gruppens handling | Efter øvelse 2 | ~30 min |

Alle i gruppen laver øvelserne på egen maskine, mod **gruppens** database fra
lektion 15. Når én opretter en patient, ser alle den — og en CPR, som én har
oprettet, kan de andre ikke oprette igen. Brug derfor hver jeres egen
syntetiske CPR, når I tester `POST`. Én i gruppen committer gruppens version.
De andre puller og laver deres egen `.env`.

---

## Øvelse 1 — Listen husker

**Mål:** `GET /api/patients` i gruppens `server.js` henter fra tabellen i
Neon. Mockuppets tabel viser de fem testpatienter, også efter en genstart.

Arbejd i gruppens projekt, i mappen med `server.js` fra lektion 13.

### Det skal du lave

1. Installér `pg`:

```bash
npm install pg
```

2. Lav `.env` ved siden af `server.js` med **gruppens** connection-string fra
   lektion 15, øvelse 3. Ret `sslmode=require` til `sslmode=verify-full`.

```text
DATABASE_URL=postgresql://neondb_owner:…@…neon.tech/neondb?sslmode=verify-full&channel_binding=require
```

3. Tilføj `.env` i `.gitignore`, ved siden af `node_modules/`. Kør
   `git status`. `.env` må **ikke** stå på listen.
4. Lav `patients.js` ved siden af `server.js`, som på slidesne: `Pool`,
   `pool.on("error", …)` og `hentPatienter()`. Eksportér den med
   `module.exports`.
5. I `server.js`:
   - slet arrayet `patients`. Har I `GET /api/patients/:cpr` fra lektion 13,
     skal den kalde `hentPatient(cpr)` (ekstra-opgaven nedenfor) eller tages
     ud, indtil den gør det. Ellers læser den et array, der ikke findes.
   - slet den gamle `POST`-route med `patients.push` — I skriver den igen i
     øvelse 2
   - `require("./patients")` øverst
   - gør `GET /api/patients` `async`, kald `hentPatienter()`, og svar `500`
     med `{ error: "serverfejl" }` i `catch`
6. Start serveren:

```bash
node --env-file=.env server.js
```

7. Åbn `http://localhost:3000/api/patients` i browseren. Du skal se de fem
   testpatienter fra Neon.
8. Åbn mockuppet lokalt. Tabellen skal vise de samme fem uden at `fetch`
   er ændret.
9. Stop serveren med Ctrl+C, og start den igen. Hent igen. Patienterne er
   der stadig.
10. Ejeren af databasen indsætter en patient i Neons SQL-editor. Genindlæs
    mockuppet hos en **anden** i gruppen. Den nye patient står i tabellen.

### Tjekliste

- [ ] `git status` viser ikke `.env`
- [ ] `server.js` har ikke længere et `patients`-array
- [ ] `server.js` indeholder ingen SQL. Al SQL står i `patients.js`
- [ ] Tabellen i mockuppet viser data fra Neon
- [ ] Genstart af serveren ændrer ikke listen

**Øvelsen er i hus**, når en anden i gruppen kan pege på, hvor SQL'en står,
og en række fra SQL-editoren dukker op i jeres tabel.

### Hvis du sidder fast

- `500` og `ECONNREFUSED` i terminalen: `pg` fik ingen connection-string og
  prøver din egen maskine. Du har glemt `--env-file=.env`, linjen i `.env`
  hedder ikke præcis `DATABASE_URL=`, eller filen er lavet i PowerShell med
  `>` (forkert tegnsæt). Lav `.env` i VS Code.
- `node: .env: not found`: filen findes ikke i den mappe, du står i — eller
  den hedder `.env.txt`.
- `password authentication failed`: strengen i `.env` er forkert. Bed ejeren
  kopiere den igen med **Copy snippet**.
- `relation "patients" does not exist`: du har brugt strengen til dit eget
  projekt `lektion15` eller et projekt uden tabellen. Brug gruppens.
- `Cannot find module 'pg'`: kør `npm install pg` i den mappe, hvor
  `server.js` ligger.
- Lang `SECURITY WARNING` om `sslmode`: ret `sslmode=require` til
  `sslmode=verify-full` i `.env`.
- Serveren svarer langsomt første gang efter en pause: databasen var gået i
  dvale. Den vågner selv.
- Serveren dør med `Unhandled 'error' event` efter en pause: `pool.on("error",
  …)` mangler i `patients.js`.
- `.env` står i `git status`: den er ikke i `.gitignore`, eller den er
  allerede committet. Spørg underviseren — passwordet skal så nulstilles.

### Hvis du har ekstra tid

- Tilføj `hentPatient(cpr)` i repositoryet med `WHERE cpr = $1`. Hvad
  returnerer den, når CPR'en ikke findes?
- Tegn sekvensdiagrammet fra slidesne for jeres egen `GET`, med jeres egne
  filnavne.

---

## Øvelse 2 — `POST` og testskemaet

**Mål:** `POST /api/patients` gemmer i tabellen. I har kørt testskemaet fra
`api.http` og skrevet resultatet i projektloggen.

### Det skal du lave

1. Tilføj `opretPatient(cpr, navn)` i `patients.js` med `INSERT … RETURNING`
   og `$1`, `$2`. Eksportér den.
2. Tilføj `POST /api/patients` i `server.js`, **over** 404-handleren:
   `req.body ?? {}`, `400` før databasen, `201` med den nye patient, `500` i
   `catch`.
3. Genstart serveren.
4. Åbn `api.http` fra lektion 13, og ret body i `POST`-requestet til en
   syntetisk patient, du kan genkende. Brug din **egen** CPR — skift de to
   sidste cifre, så den ikke er den samme som gruppens andres:

```http
### Opret patient
POST http://localhost:3000/api/patients
Content-Type: application/json

{"cpr":"0201029994","navn":"Ny Test Person"}
```

5. Kør testskemaet. Skriv resultatet i kolonnen **Fik**.

| # | Request | Forventet | Fik |
|---|---|---|---|
| 1 | `GET /api/patients` | `200` og listen | |
| 2 | `POST /api/patients` med body | `201` og patienten | |
| 3 | `POST /api/patients` uden body | `400` | |
| 4 | `GET /api/patients` efter genstart | `200`, patienten fra 2 er der | |
| 5 | `GET /api/patients` med forkert password i `.env` | `500` og `serverfejl` | |
| 6 | `GET /findesikke` | `404` | |

Requests i `api.http` — de tre første har I fra lektion 13, tilføj den
sidste:

```http
### Hele listen
GET http://localhost:3000/api/patients

### Opret patient
POST http://localhost:3000/api/patients
Content-Type: application/json

{"cpr":"0201029994","navn":"Ny Test Person"}

### Uden body — skal give 400
POST http://localhost:3000/api/patients

### Ukendt sti — skal give 404
GET http://localhost:3000/findesikke
```

Statuskoden står øverst i svaret ved siden af filen.

Til række 5: skift ét tegn i passwordet i `.env`, genstart, kør `GET`, og
læs fejlen i **terminalen** — ikke i svaret. Sæt passwordet tilbage, og
genstart igen.

6. Kopiér skemaet med jeres resultater ind i projektloggen.

### Tjekliste

- [ ] `opretPatient` bruger `$1` og `$2` — ingen `+` i SQL'en
- [ ] `400` kommer, før databasen bliver kaldt
- [ ] Alle seks rækker i skemaet giver det forventede
- [ ] Svaret på række 5 viser ikke en stacktrace, kun `serverfejl`
- [ ] Passwordet i `.env` er sat tilbage

**Øvelsen er i hus**, når skemaet står i projektloggen med seks rigtige
resultater, og I kan forklare forskellen på `400` og `500`.

### Hvis du sidder fast

- Ingen **Send Request** over requestet: REST Client er ikke installeret i
  VS Code, eller filen ender ikke på `.http`.
- `400` med `Expected property name`: JSON'en i body er ugyldig. Tjek
  anførselstegn og kommaer.
- `POST` giver `404`: routen står under 404-handleren. Flyt den op.
- `500` på et `POST`, der burde virke: læs fejlen i terminalen. `duplicate
  key` betyder, at CPR'en allerede findes — fra en tidligere kørsel eller fra
  en anden i gruppen. Skift CPR i body i `api.http`.

### Hvis du har ekstra tid

- Send samme `POST` to gange. Det andet svar er `500`, fordi `UNIQUE`
  på `cpr` afviser rækken. Er `500` den rigtige kode? Klienten har jo sendt
  noget forkert, ikke serveren. Find fejlkoden i terminalen (`23505`), og
  diskutér i gruppen, hvilken statuskode der passer bedre.
- Skift CPR i body i `api.http` igen, og sæt navnet til
  `x'); DELETE FROM patients; --`. Send den. Hvad står der i tabellen
  bagefter? Hvorfor er de andre patienter der stadig?

---

## Øvelse 3 — Gruppens handling

**Mål:** Gruppens centrale brugerhandling når hele vejen ned i tabellen: en
formular i mockuppet sender `POST`, og serveren gemmer i gruppens egen tabel
fra lektion 15. Det er kernen i D2.

Brug syntetiske data. Ingen rigtige CPR, navne eller målinger.

### Det skal I nå

1. Installér `cors`, og erstat jeres egen
   `Access-Control-Allow-Origin`-middleware fra lektion 13. `app.use(cors())`
   skal stå før jeres routes:

```bash
npm install cors
```

```js
const cors = require("cors");
app.use(cors());
```

2. Lav et repository til gruppens tabel — en ny fil, opkaldt efter tabellen,
   fx `maalinger.js`. Én funktion, der opretter en række med
   `INSERT … RETURNING` og pladsholdere. Brug `patients.js` som skabelon.
3. Lav `POST /api/<jeres tabel>` i `server.js`. Samme mønster som
   patienter: `400` før databasen, `201`, `500` i `catch`.
4. Test routen fra `api.http`, før I rører HTML'en.
5. I mockuppet: på den side, hvor brugeren laver gruppens handling, lav en
   formular, der sender `POST` med `fetch`. Udgangspunktet nedenfor er til
   patienter — ret felterne og URL'en til jeres tabel.
6. Åbn DevTools → **Network**, og send formularen. I skal se `OPTIONS` med
   `204` og derefter `POST` med `201`.
7. Ejeren af databasen finder rækken i Neons SQL-editor med `SELECT`.

### Udgangspunkt

```html
<form id="ny-patient">
  <label for="cpr">CPR</label>
  <input id="cpr" type="text">
  <label for="navn">Navn</label>
  <input id="navn" type="text">
  <p id="fejl" hidden>Kunne ikke gemme. Tjek felterne.</p>
  <p id="ok" hidden>Gemt.</p>
  <button type="submit">Gem</button>
</form>
```

```js
const form = document.getElementById("ny-patient");
const fejl = document.getElementById("fejl");
const ok = document.getElementById("ok");

form.addEventListener("submit", async (event) => {
  event.preventDefault();
  fejl.hidden = true;
  ok.hidden = true;
  try {
    const res = await fetch("http://localhost:3000/api/patients", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        cpr: document.getElementById("cpr").value,
        navn: document.getElementById("navn").value
      })
    });
    if (!res.ok) {
      fejl.hidden = false;
      return;
    }
    ok.hidden = false;
    form.reset();
  } catch (err) {
    fejl.hidden = false;
  }
});
```

`try/catch` fanger, at serveren slet ikke svarer — fx fordi den ikke kører.
`!res.ok` fanger `400` og `500`.

### Tjekliste

- [ ] Gruppens tabel har sin egen repository-fil. `server.js` indeholder
  ingen SQL
- [ ] Alle værdier fra requestet går gennem `$1`, `$2`, …
- [ ] Formularen viser en fejl, hvis et felt mangler (`400`)
- [ ] Network viser `OPTIONS` `204` og `POST` `201`
- [ ] Rækken står i tabellen i Neon

**Øvelsen er i hus**, når en anden i gruppen kan udfylde formularen og ejeren
kan vise rækken i SQL-editoren.

### Hvis I sidder fast

- `OPTIONS` med `404` i Network: `app.use(cors())` mangler, eller den står
  under 404-handleren.
- `OPTIONS` med `204`, men `POST` bliver blokeret af CORS i Console:
  `app.use(cors())` står under jeres routes. Flyt den op, før routes.
- Siden genindlæser ved submit: `event.preventDefault()` mangler.
- `400` hver gang: tjek, at nøglerne i `JSON.stringify({ … })` hedder det
  samme som det, routen læser fra `req.body`.
- Fejlbeskeden kommer, og Network viser intet `POST`: serveren kører ikke.
- Gruppens tabel har en fremmednøgle til `patients`: send `patient_id` med i
  body, og tjek i routen, at den er der.

### Hvis I har ekstra tid

- Lav `GET /api/<jeres tabel>` og en funktion i repositoryet, der henter
  rækkerne. Vis dem på siden, og hent igen efter `201` — samme mønster som
  patienttabellen fra lektion 7. Genstart serveren, og se, at rækkerne er der
  stadig.
- Skriv et testskema som i øvelse 2 for gruppens egne routes, og læg det i
  projektloggen.
- Tegn sekvensdiagrammet for jeres handling: bruger, browser, route,
  repository, Neon. Det kan bruges direkte i D2.
